在《Effective Java》中,Joshua Bloch 寫了 9 個關(guān)于如何在 Java 中處理異常的技巧。這些技巧已經(jīng)成為 Java 異常處理的事實上的標(biāo)準(zhǔn)。在這篇文章中,我列出了一些開源項目中Java異常處理的一些例子,并按照異常處理的9個技巧來評論用法。
Java異常處理的9個技巧是:
1. 僅在異常情況下使用異常 2. 對可恢復(fù)條件使用檢查異常,對編程錯誤使用運行時異常 3. 避免不必要的使用受檢異常 4. 贊成使用標(biāo)準(zhǔn)異常 5. 拋出適合抽象的異常 6. 記錄每個方法拋出的所有異常 7. 在詳細(xì)消息中包含故障捕獲信息 8. 力求故障原子性 9. 不要忽視異常
1. 僅在異常情況下使用異常
此項主要是避免對普通控制流使用異常。
例如,不是使用異常來終止循環(huán)控制流:
try{
Iterator<Foo> iter = ...;
while(true) {
Foo foo = i.next();
...
}
} catch (NoSuchElementException e){
}
應(yīng)該使用對集合的常規(guī)迭代:
for(Iterator<Foo> iter = ...; i.hasNext();){
Foo foo = i.next();
...
}
我沒有找到任何使用常規(guī)控制流異常的示例。
2. 對可恢復(fù)條件使用檢查異常,對編程錯誤使用運行時異常
大多數(shù)情況下,如果調(diào)用者可以恢復(fù)異常,則應(yīng)使用已檢查的異常。如果不是,則應(yīng)使用運行時異常。運行時異常表示可以通過檢查某些先決條件(例如數(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. 避免不必要的使用受檢異常
檢查異常強(qiáng)制調(diào)用者處理異常情況,因為如果沒有,編譯器會抱怨。過度使用檢查異常會給調(diào)用者帶來處理異常情況的負(fù)擔(dān)。所以必要時應(yīng)該使用受檢異常。使用受檢異常的經(jīng)驗法則是,當(dāng)無法通過檢查前提條件避免異常時,調(diào)用者可以采取一些有用的操作來處理異常。
常用的運行時異常本身就是不要過度使用檢查異常的例子。在常見的運行時異常有:?ArithmeticException
?,?ClassCastException
?異常,拋出:?IllegalArgumentException
?,?IllegalStateException
?異常,?IndexOutOfBoundExceptions
?,?NoSuchElementException
?異常,和NullPointerException
?異常。
在下面的方法中,當(dāng)?propertyName
?不是目標(biāo)情況之一時,調(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. 贊成使用標(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 名中沒有一個是書中顯示的最常用的。但是要注意,這些是按項目計算的,即如果一個類在一個項目中使用,無論項目中有多少方法在使用它,它都只計算一次。所以這是按項目數(shù)計算,但按代碼中出現(xiàn)的次數(shù)計算。
5. 拋出適合抽象的異常
拋出的異常應(yīng)該與調(diào)用者執(zhí)行的任務(wù)有聯(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. 記錄每個方法拋出的所有異常
這是嚴(yán)重使用不足。大多數(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 {
...
這是一個缺乏有關(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
?使用不同的字符串來傳遞不同的故障捕獲信息。
8.力求故障原子性
第 8 項是關(guān)于失敗的。一般規(guī)則是失敗的方法不應(yīng)該改變方法中對象的狀態(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)代碼中幾乎總是應(yīng)該避免打印堆棧跟蹤。這與忽略異常一樣糟糕。這將寫入標(biāo)準(zhǔn)錯誤流,這不是日志使用日志記錄框架的地方。