讀書,收獲,分享
建議后面的五角星僅代表筆者個人需要注意的程度。
Talk is cheap.Show me the code
建議110:提倡異常封裝★★☆☆☆
Java API提供的異常只有開發(fā)人員才能看得懂,而對于終端用戶來說,這些異?;旧暇褪翘鞎窃撛趺崔k?這就需要我們對異常進行封裝了。異常封裝有三方面的優(yōu)點:
-
提高系統(tǒng)的友好性,如下:
public static void doStuff2() throws MyBussinessException{ try { InputStream is = new FileInputStream("無效文件.txt"); } catch (FileNotFoundException e) { //為方便開發(fā)和維護人員而設置的異常信息 e.printStackTrace(); //拋出業(yè)務異常 throw new MyBussinessException(e); } /*文件操作*/ } -
提高系統(tǒng)的可維護性,如下:
public void doStuff2(){ try{ //do something }catch(FileNotFoundException e){ log.info("文件問找到,使用默認配置文件……"); }catch(SecurityException e){ log.error("無權訪問,可能原因是……"); e.printStackTrace(); } } -
解決Java異常機制自身的缺陷(Java中的異常一次只能拋出一個)。如下:
class MyException extends Exception { // 容納所有的異常 private List<Throwable> causes = new ArrayList<Throwable>(); // 構造函數,傳遞一個異常列表 public MyException(List<? extends Throwable> _causes) { causes.addAll(_causes); } // 讀取所有的異常 public List<Throwable> getExceptions() { return causes; } } public static void doStuff() throws MyException { List<Throwable> list = new ArrayList<Throwable>(); // 第一個邏輯片段 try { // Do Something } catch (Exception e) { list.add(e); } // 第二個邏輯片段 try { // Do Something } catch (Exception e) { list.add(e); } if (list.size() > 0) { throw new MyException(list); } }
建議111:采用異常鏈傳遞異常★★☆☆☆
設計模式中有一個模式叫做責任鏈模式(Chain of Responsibility),它的目的是將多個對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止,異常的傳遞處理也應該采用責任鏈模式。
對于異常先封裝,然后傳遞,過程如下:
- 把
FileNotFoundException封裝為MyException。 - 拋出到邏輯層,邏輯層根據異常代碼(或者自定義的異常類型)確定后續(xù)處理邏輯,然后拋出到展現層。
- 展現層自行決定要展現什么,如果是管理員則可以展現低層級的異常,如果是普通用戶則展示封裝后的異常。
示例如下:
public class IOException extends Exception {
public IOException() {
super();
}
//定義異常原因
public IOException(String message) {
super(message);
}
//定義異常原因,并攜帶原始異常
public IOException(String message, Throwable cause) {
super(message, cause);
}
//保留原始異常信息
public IOException(Throwable cause) {
super(cause);
}
}
//調用
try{
//doSomething
}catch(Exception e){
throw new IOException(e);
}
建議112:受檢異常盡可能轉化為非受檢異常★☆☆☆☆
受檢異常不足的地方:
-
受檢異常使接口聲明脆弱
interface User{ //修改用戶密碼,拋出安全異常 public void changePassword() throws MySecurityException; } //這會導致所有的User調用者都要追加對RejectChangeException異常問題的處理。 -
受檢異常使代碼的可讀性降低
public static void main(String[] args) { //調用者必須對異常進行處理 try{ user.changePassword(); }catch(Exception e){ e.printStackTrace(); } } 受檢異常增加了開發(fā)工作量
注意:受檢異常威脅到系統(tǒng)的安全性、穩(wěn)定性、可靠性、正確性時,不能轉換為非受檢異常。
建議113:不要在finally塊中處理返回值★☆☆☆☆
可能會引發(fā)以下問題:
-
覆蓋了
try代碼塊中的return返回值public static Person doStuffw() { Person person = new Person(); person.setName("張三"); try { return person; } catch (Exception e) { } finally { // 重新修改一下值 person.setName("李四"); } person.setName("王五"); return person; } -
屏蔽異常
public static void doSomeThing(){ try{ //正常拋出異常 throw new RuntimeException(); }finally{ //告訴JVM:該方法正常返回 return; } }
注意:不要在
finally代碼塊中出現return語句。
建議114:不要在構造函數中拋出異常★☆☆☆☆
Java的異常機制有三種:
-
Error類及其子類表示的是錯誤,無法處理。 -
RuntimeException類及其子類表示的是非受檢異常,需要處理。 -
Exception類及其子類(不包含非受檢異常)表示的是受檢異常,必須處理。
從系統(tǒng)設計和開發(fā)的角度來分析,盡量不要在構造函數中拋出異常,原因分析:
構造函數拋出錯誤是程序員無法處理的
-
構造函數不應該拋出非受檢異常
- 加重了上層代碼編寫者的負擔
- 后續(xù)代碼不會執(zhí)行
-
構造函數盡可能不要拋出受檢異常
- 導致子類代碼膨脹
- 違背了里氏替換原則,里氏替換原則是說“父類能出現的地方子類就可以出現,而且將父類替換為子類也不會產生任何異?!?/li>
- 子類構造函數擴展受限
建議115:使用Throwable獲得棧信息★☆☆☆☆
JVM在創(chuàng)建一個Throwable類及其子類時會把當前線程的棧信息記錄下來,以便在輸出異常時準確定位異常原因,我們來看Throwable源代碼:
public class Throwable implements Serializable {
//出現異常記錄的棧幀
private StackTraceElement[] stackTrace;
//默認構造函數
public Throwable() {
//記錄棧幀
fillInStackTrace();
}
//本地方法,抓取執(zhí)行時的棧信息
private synchronized native Throwable fillInStackTrace();
}
在出現異常時(或主動聲明一個Throwable對象時),JVM會通過fillInStackTrace方法記錄下棧幀信息,然后生成一個Throwable對象,這樣我們就可以知道類間的調用順序、方法名稱及當前行號等了。
建議116:異常只為異常服務★☆☆☆☆
異常雖然是描述例外事件的,但能避免則避免。
建議在異常誕生前就消除掉,比如增加判斷,以提高程序的性能和穩(wěn)定性。
注意:異常只為確實異常的事件服務。
建議117:多使用異常,把性能問題放一邊★☆☆☆☆
異常有一個缺點:性能比較慢。
Java的異常處理機制確實比較慢,這個“比較慢”是相對于諸如String、Integer等對象來說的,單單從對象的創(chuàng)建上來說,new一個IOException會比String慢5倍,這從異常的處理機制上也可以解釋:因為它要執(zhí)行fillInStackTrace方法,要記錄當前棧的快照,而String類則是直接申請一個內存創(chuàng)建對象,異常類慢一籌也就在所難免了。而且,異常類是不能緩存的。
經過測試,在JDK 1.6下,一個異常對象創(chuàng)建的時間只需要1.4毫秒左右(注意是毫秒,通常一個交易處理是在100毫秒左右),難道我們的系統(tǒng)連如此微小的性能消耗都不允許嗎?
注意:性能問題不是拒絕異常的借口。