App下載

開源項目中的 Java 異常處理示例

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

《Effective Java》中,Joshua Bloch 寫了 9 個關于如何在 Java 中處理異常的技巧。這些技巧已經(jīng)成為 Java 異常處理的事實上的標準。在這篇文章中,我列出了一些開源項目中Java異常處理的一些例子,并按照異常處理的9個技巧來評論用法。

Java異常處理的9個技巧是:

1. 僅在異常情況下使用異常
2. 對可恢復條件使用檢查異常,對編程錯誤使用運行時異常
3. 避免不必要的使用受檢異常
4. 贊成使用標準異常
5. 拋出適合抽象的異常
6. 記錄每個方法拋出的所有異常
7. 在詳細消息中包含故障捕獲信息
8. 力求故障原子性
9. 不要忽視異常

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

此項主要是避免對普通控制流使用異常。

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

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

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

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

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

2. 對可恢復條件使用檢查異常,對編程錯誤使用運行時異常

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

在下面的方法中,?IllegalArgumentException ?是一個 ?RuntimeException?,它的用法表示編程錯誤。通??梢酝ㄟ^檢查前提條件來避免編程錯誤。所以這是基于這個技巧的一個不好的例子??梢酝ㄟ^檢查先決條件來避免異常,即這里的“?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. 避免不必要的使用受檢異常

檢查異常強制調(diào)用者處理異常情況,因為如果沒有,編譯器會抱怨。過度使用檢查異常會給調(diào)用者帶來處理異常情況的負擔。所以必要時應該使用受檢異常。使用受檢異常的經(jīng)驗法則是,當無法通過檢查前提條件避免異常時,調(diào)用者可以采取一些有用的操作來處理異常。

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

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

@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. 贊成使用標準異常

最常重用的 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 名中沒有一個是書中顯示的最常用的。但是要注意,這些是按項目計算的,即如果一個類在一個項目中使用,無論項目中有多少方法在使用它,它都只計算一次。所以這是按項目數(shù)計算,但按代碼中出現(xiàn)的次數(shù)計算。

5. 拋出適合抽象的異常

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

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?,并重新拋出一個適合方法抽象級別的新異常。新的 ?BillingRunFailed? 異常包裝了原始異常。所以這是異常鏈的一個很好的例子。異常鏈的好處是保留有助于調(diào)試問題的低級異常。

6. 記錄每個方法拋出的所有異常

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

這是一個很好的例子。

...
 *
 * @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 {
...

這是一個缺乏有關在什么情況下拋出異常的信息的壞例子。

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

7. 在詳細消息中包含故障捕獲信息

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 ?使用不同的字符串來傳遞不同的故障捕獲信息。

8.力求故障原子性

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

/**
 * 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)代碼中幾乎總是應該避免打印堆棧跟蹤。這與忽略異常一樣糟糕。這將寫入標準錯誤流,這不是日志使用日志記錄框架的地方。


0 人點贊