App下載

開源項(xiàng)目中的 Java 異常處理示例

可樂加冰塊 2021-09-03 11:39:54 瀏覽數(shù) (2194)
反饋

《Effective Java》中,Joshua Bloch 寫了 9 個(gè)關(guān)于如何在 Java 中處理異常的技巧。這些技巧已經(jīng)成為 Java 異常處理的事實(shí)上的標(biāo)準(zhǔn)。在這篇文章中,我列出了一些開源項(xiàng)目中Java異常處理的一些例子,并按照異常處理的9個(gè)技巧來(lái)評(píng)論用法。

Java異常處理的9個(gè)技巧是:

1. 僅在異常情況下使用異常
2. 對(duì)可恢復(fù)條件使用檢查異常,對(duì)編程錯(cuò)誤使用運(yùn)行時(shí)異常
3. 避免不必要的使用受檢異常
4. 贊成使用標(biāo)準(zhǔn)異常
5. 拋出適合抽象的異常
6. 記錄每個(gè)方法拋出的所有異常
7. 在詳細(xì)消息中包含故障捕獲信息
8. 力求故障原子性
9. 不要忽視異常

1. 僅在異常情況下使用異常

此項(xiàng)主要是避免對(duì)普通控制流使用異常。

例如,不是使用異常來(lái)終止循環(huán)控制流:

try{
  Iterator<Foo> iter = ...;
  while(true) {
    Foo foo = i.next();
    ...
  }
} catch (NoSuchElementException e){
}

應(yīng)該使用對(duì)集合的常規(guī)迭代:

for(Iterator<Foo> iter = ...; i.hasNext();){
  Foo foo = i.next();
  ...
}

我沒有找到任何使用常規(guī)控制流異常的示例。

2. 對(duì)可恢復(fù)條件使用檢查異常,對(duì)編程錯(cuò)誤使用運(yùn)行時(shí)異常

大多數(shù)情況下,如果調(diào)用者可以恢復(fù)異常,則應(yīng)使用已檢查的異常。如果不是,則應(yīng)使用運(yùn)行時(shí)異常。運(yùn)行時(shí)異常表示可以通過(guò)檢查某些先決條件(例如數(shù)組邊界和空性檢查)來(lái)防止的編程錯(cuò)誤。

在下面的方法中,?IllegalArgumentException ?是一個(gè) ?RuntimeException?,它的用法表示編程錯(cuò)誤。通常可以通過(guò)檢查前提條件來(lái)避免編程錯(cuò)誤。所以這是基于這個(gè)技巧的一個(gè)不好的例子??梢酝ㄟ^(guò)檢查先決條件來(lái)避免異常,即這里的“?hasNext()?”方法。

/**
 * Convert a tag string into a tag map.
 *
 * @param tagString a space-delimited string of key-value pairs. For example, {@code "key1=value1 key_n=value_n"}
 * @return a tag {@link Map}
 * @throws IllegalArgumentException if the tag string is corrupted.
 */
public static Map<String, String> parseTags(final String tagString) throws IllegalArgumentException {
    // delimit by whitespace or '='
    Scanner scanner = new Scanner(tagString).useDelimiter("\\s+|=");
 
    Map<String, String> tagMap = new HashMap<String, String>();
    try {
        while (scanner.hasNext()) {
            String tagName = scanner.next();
            String tagValue = scanner.next();
            tagMap.put(tagName, tagValue);
        }
    } catch (NoSuchElementException e) {
        // The tag string is corrupted.
        throw new IllegalArgumentException("Invalid tag string '" + tagString + "'");
    } finally {
        scanner.close();
    }
 
    return tagMap;
}

3. 避免不必要的使用受檢異常

檢查異常強(qiáng)制調(diào)用者處理異常情況,因?yàn)槿绻麤]有,編譯器會(huì)抱怨。過(guò)度使用檢查異常會(huì)給調(diào)用者帶來(lái)處理異常情況的負(fù)擔(dān)。所以必要時(shí)應(yīng)該使用受檢異常。使用受檢異常的經(jīng)驗(yàn)法則是,當(dāng)無(wú)法通過(guò)檢查前提條件避免異常時(shí),調(diào)用者可以采取一些有用的操作來(lái)處理異常。

常用的運(yùn)行時(shí)異常本身就是不要過(guò)度使用檢查異常的例子。在常見的運(yùn)行時(shí)異常有:?ArithmeticException?,?ClassCastException?異常,拋出:?IllegalArgumentException?,?IllegalStateException?異常,?IndexOutOfBoundExceptions?,?NoSuchElementException?異常,和NullPointerException?異常。

在下面的方法中,當(dāng)?propertyName?不是目標(biāo)情況之一時(shí),調(diào)用者可以做的事情不多,因此拋出運(yùn)行時(shí)異常。

@Override
public Object get(String propertyName) {
  switch (propertyName.hashCode()) {
    case 842855857:  // marketDataName
      return marketDataName;
    case -1169106440:  // parameterMetadata
      return parameterMetadata;
    case 106006350:  // order
      return order;
    case 575402001:  // currency
      return currency;
    case 564403871:  // sensitivity
      return sensitivity;
    default:
      throw new NoSuchElementException("Unknown property: " + propertyName);
  }
}

4. 贊成使用標(biāo)準(zhǔn)異常

最常重用的 Java 異常類如下:

1.java.io.IO異常
2.java.io.FileNotFoundException
3.java.io.UnsupportedEncodingException
4. java.lang.reflect.InvocationTargetException
5.java.security.NoSuchAlgorithmException
6.java.net.MalformedURLException
7.java.text.ParseException
8. java.net.URISyntaxException
9. java.util.concurrent.ExecutionException
10. java.net.UnknownHostException

前 10 名中沒有一個(gè)是書中顯示的最常用的。但是要注意,這些是按項(xiàng)目計(jì)算的,即如果一個(gè)類在一個(gè)項(xiàng)目中使用,無(wú)論項(xiàng)目中有多少方法在使用它,它都只計(jì)算一次。所以這是按項(xiàng)目數(shù)計(jì)算,但按代碼中出現(xiàn)的次數(shù)計(jì)算。

5. 拋出適合抽象的異常

拋出的異常應(yīng)該與調(diào)用者執(zhí)行的任務(wù)有聯(lián)系。此項(xiàng)介紹異常轉(zhuǎn)換(捕獲異常并拋出另一個(gè))和異常鏈(將異常包裝在新的異常中以保留異常的因果鏈)。

private void serializeBillingDetails(BillingResult billingResult,
        BillingDetailsType billingDetails) {
 
    try {
        final JAXBContext context = JAXBContext
                .newInstance(BillingdataType.class);
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty("jaxb.formatted.output", Boolean.FALSE);
        final BillingdataType billingdataType = new BillingdataType();
        billingdataType.getBillingDetails().add(billingDetails);
        marshaller.marshal(factory.createBillingdata(billingdataType), out);
        final String xml = new String(out.toByteArray(), "UTF-8");
        billingResult.setResultXML(xml.substring(
                xml.indexOf("<Billingdata>") + 13,
                xml.indexOf("</Billingdata>")).trim());
        billingResult.setGrossAmount(billingDetails.getOverallCosts()
                .getGrossAmount());
        billingResult.setNetAmount(billingDetails.getOverallCosts()
                .getNetAmount());
    } catch (JAXBException | UnsupportedEncodingException ex) {
        throw new BillingRunFailed(ex);
    }
}

上述方法捕獲 ?JAXBException ?和 ?UnsupportedEncodingException?,并重新拋出一個(gè)適合方法抽象級(jí)別的新異常。新的 ?BillingRunFailed? 異常包裝了原始異常。所以這是異常鏈的一個(gè)很好的例子。異常鏈的好處是保留有助于調(diào)試問題的低級(jí)異常。

6. 記錄每個(gè)方法拋出的所有異常

這是嚴(yán)重使用不足。大多數(shù)公共 API 都沒有 @throws Java 文檔來(lái)解釋拋出的異常。

這是一個(gè)很好的例子。

...
 *
 * @throws MalformedURLException The formal system identifier of a
 * subordinate catalog cannot be turned into a valid URL.
 * @throws IOException Error reading subordinate catalog file.
 */
public String resolveSystem(String systemId)
  throws MalformedURLException, IOException {
...

這是一個(gè)缺乏有關(guān)在什么情況下拋出異常的信息的壞例子。

 * @throws Exception exception
 */
public void startServer() throws Exception {
    if (!externalDatabaseHost) {

7. 在詳細(xì)消息中包含故障捕獲信息

private OutputStream openOutputStream(File file) throws IOException {
    if (file.exists()) {
        if (file.isDirectory()) {
            throw new IOException("File '" + file + "' exists but is a directory");
        }
        if (!file.canWrite()) {
            throw new IOException("File '" + file + "' cannot be written to");
        }
    } else {
        final File parent = file.getParentFile();
        if (parent != null) {
            if (!parent.mkdirs() && !parent.isDirectory()) {
                throw new IOException("Directory '" + parent + "' could not be created");
            }
        }
    }
    return new FileOutputStream(file, false);
}

在該方法中,?IOException ?使用不同的字符串來(lái)傳遞不同的故障捕獲信息。

8.力求故障原子性

第 8 項(xiàng)是關(guān)于失敗的。一般規(guī)則是失敗的方法不應(yīng)該改變方法中對(duì)象的狀態(tài)。為了盡早失敗,一種方法是在執(zhí)行操作之前檢查參數(shù)的有效性。以下是遵循此提示的一個(gè)很好的示例。

/**
 * Assigns a new int value to location index of the buffer instance.
 * @param index int
 * @param newValue int
 */
public void modifyEntry(int index, int newValue) {
        if (index < 0 || index > size - 1) {
            throw new IndexOutOfBoundsException();
        }
 
//        ((int[]) bufferArrayList.get((int) (index / pageSize)))[index % pageSize] =
        ((int[]) bufferArrayList.get((index >> exp)))[index & r] =
            newValue;
}

9. 不要忽視異常

public static Bundle decodeUrl(String s) {
    Bundle params = new Bundle();
    if (s != null) {
        String array[] = s.split("&");
        for (String parameter : array) {
            String v[] = parameter.split("=");
            try {
                params.putString(URLDecoder.decode(v[0], "UTF-8"), URLDecoder.decode(v[1], "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    }
    return params;
}

在生產(chǎn)代碼中幾乎總是應(yīng)該避免打印堆棧跟蹤。這與忽略異常一樣糟糕。這將寫入標(biāo)準(zhǔn)錯(cuò)誤流,這不是日志使用日志記錄框架的地方。


0 人點(diǎn)贊