Java基礎(chǔ)之異常機(jī)制學(xué)習(xí)&分析
Java異常機(jī)制學(xué)習(xí)&分析
處理錯(cuò)誤
Java異常層次簡(jiǎn)要類圖
何時(shí)聲明受查異常
調(diào)用一個(gè)拋出受查異常的方法,例如, FileInputStream構(gòu)造器
程序運(yùn)行過程中發(fā)現(xiàn)錯(cuò)誤,并且利用throw語(yǔ)句拋出一個(gè)受查異常
程序出現(xiàn)錯(cuò)誤,例如,a[-1]=0會(huì)拋出一個(gè)ArrayIndexOutOfBoundsException這樣的非受查異常。
Java 虛擬機(jī)和運(yùn)行時(shí)庫(kù)出現(xiàn)的內(nèi)部錯(cuò)誤。
如果出現(xiàn)前兩種情況之一,則必須告訴調(diào)用這個(gè)放啊的程序員有可能拋出異常。為什么?因?yàn)槿魏我粋€(gè)拋出異常的方法都可能是一個(gè)死亡陷阱。
如果沒有處理器捕獲這個(gè)異常,當(dāng)前執(zhí)行的線程就會(huì)結(jié)束。
如下所示:
public Image loadImage(String s) throws IOException {
return null;
}
public Image loadImage2(String s) throws FileNotFoundException,EOFException {
return null;
}
如果在子類中覆蓋了超類的一個(gè)方法,子類方法中聲明的受查異常不能比超類方法中聲明的異常更通用(也就是說,子類方法中可以
拋出更特定的異常,或者根本不拋出任何異常)。特別需要說明的是,如果超類方法沒有拋出任何受查異常,子類也不能拋出任何受查異常。
自定義異常類
public class FileFormatException extends IOException {
public FileFormatException() {
}
public FileFormatException(String msg) {
super(msg);
}
}
捕獲異常
以下代碼:
FileInputStream in = new FileInputStream("test.txt");
try {
//1
// code that might throw exception
//2
} catch (IOException e) {
//3
//show error message
//4
}
finally {
//5
//in.close();
}
//6
有下列3中情況會(huì)執(zhí)行finally子句
1. 代碼沒有拋出異常,在這種情況下,程序首先執(zhí)行try語(yǔ)句塊中的全部代碼,然后執(zhí)行finally子句
中的代碼,隨后,繼續(xù)執(zhí)行try語(yǔ)句塊之后的第一條語(yǔ)句。也就是說,執(zhí)行順序:1,2,5,6
2. 拋出一個(gè)在catch子句中捕獲的異常。在上面的實(shí)例中就是IOException異常。在這種情況下,程序?qū)?br>執(zhí)行try語(yǔ)句塊中的所有代碼,知道發(fā)生異常為止。此時(shí),將跳過try語(yǔ)句塊中的剩余代碼,轉(zhuǎn)去
執(zhí)行與該異常匹配的catch子句中的代碼,最后執(zhí)行finally子句中的代碼。
如果catch子句沒有拋出異常,程序?qū)?zhí)行try語(yǔ)句塊之后的第一條語(yǔ)句,在這里,執(zhí)行順序:1,3,4,5,6
如果catch子句拋出了一個(gè)異常,異常江北拋回這個(gè)方法的調(diào)用者。在這里,執(zhí)行順序是:1,3,5
3. 代碼拋出了一個(gè)異常,但這個(gè)異常不是由catch子句捕獲的。在這種情況下,程序?qū)?zhí)行try語(yǔ)句塊中的所有
語(yǔ)句,知道有異常被拋出位置。此時(shí)將跳過try語(yǔ)句塊中的剩余代碼,然后執(zhí)行finally子句中的語(yǔ)句,并將異常拋給
這個(gè)方法的調(diào)用者,在這里,執(zhí)行順序:1,5
強(qiáng)烈建議
強(qiáng)烈建議解耦合try/catch和try/finally語(yǔ)句塊。這樣可以提高代碼的清晰度。例如:
FileInputStream in = new FileInputStream("test.txt");
try {
try {
// code that might throw exception
} finally {
in.close();
}
} catch (IOException e) {
//show error message
}
內(nèi)層的try語(yǔ)句塊只有一個(gè)職責(zé),就是確保關(guān)閉輸入流。外層的try語(yǔ)句塊也只有一個(gè)職責(zé),就是確保
報(bào)告出現(xiàn)的錯(cuò)誤。這種設(shè)計(jì)方式不僅清楚,而且還具有一個(gè)功能,就是將會(huì)報(bào)告finally子句中出現(xiàn)的錯(cuò)誤。
finally子句中也有返回的情況
public static void main(String[] args) {
System.out.println("n=1時(shí)方法mult返回結(jié)果:" + mult(1));
System.out.println("n=2時(shí)方法mult返回結(jié)果:" + mult(2));
}
public static int mult(int n) {
try {
int r = n * n;
return r;
} finally {
if (n == 2) {
return 0;
}
}
}
得到結(jié)果是:n=1時(shí)方法mult返回結(jié)果:1
n=2時(shí)方法mult返回結(jié)果:0
如結(jié)果所示,當(dāng)n=2時(shí),try語(yǔ)句塊的計(jì)算結(jié)果為r=4,并執(zhí)行return語(yǔ)句,然而,在方法真正返回值之前,還要執(zhí)行finally子句。finally子句將使得方法的返回值為0,這個(gè)返回值會(huì)覆蓋原始的返回值4。
### 帶資源的try語(yǔ)句
對(duì)于以下代碼模式:
open a resource
try{
work with resource
}
finally{
close the resource
}
假設(shè)資源屬于一個(gè)實(shí)現(xiàn)了AutoCloseable接口的類。Java SE 7為這種代碼提高了一個(gè)很有用的快捷方式,
AutoCloseable 接口有一個(gè)方法。
我們可以簡(jiǎn)寫成
try(Resource res=...){
work with res
}
例如:
try(InputStream inputStream = request.getInputStream()){
reqJsonStr = StreamUtils.copyToString(inputStream , Charset.defaultCharset());
}
### 分析堆棧軌跡元素
堆棧軌跡(stack trace)是一個(gè)方法調(diào)用過程的列表,它包含了程序執(zhí)行過程中方法調(diào)用的特定位置。
前面已經(jīng)看到過這種列表,當(dāng)Java程序正常終止,而沒有捕獲異常時(shí),這個(gè)列表就會(huì)顯示出來。
e.printStackTrace(); 打印堆棧信息。
例如:以下打印遞歸階乘函數(shù)的堆棧情況
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter=" + scanner);
int n = scanner.nextInt();
factorial(n);
}
public static int factorial(int n) {
System.out.println("factorial(" + n + ")");
Throwable throwable = new Throwable();
StackTraceElement[] stackTrace = throwable.getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
System.out.println(stackTraceElement);
}
int result;
if (n == 1) {
result = 1;
} else {
result = n * factorial(n - 1);
}
System.out.println("result=" + result);
return result;
}
結(jié)果是:
factorial(3)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
factorial(2)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
factorial(1)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
result=1
result=2
result=6
使用異常機(jī)制的技巧
異常處理不能代替簡(jiǎn)單的測(cè)試
不要過分地細(xì)化異常
利用異常層次結(jié)構(gòu)
不要只拋出RuntimeException異常。應(yīng)該尋找更加適當(dāng)?shù)淖宇惢蛘邉?chuàng)建自己的異常類
不要只捕獲Throwable異常,否則,會(huì)使程序代碼更難讀,更難維護(hù)。
不要壓制異常
在檢測(cè)錯(cuò)誤時(shí)?!笨量獭?要比放任更好
當(dāng)用無效參數(shù)滴啊用一個(gè)方法時(shí),返回一個(gè)虛擬的數(shù)值,還是拋出一個(gè)異常,哪種處理方式更好呢?
例如:當(dāng)棧為空時(shí),Stack.pop是返回一個(gè)null,還是拋出一個(gè)異常?我們認(rèn)為:在出錯(cuò)的地方拋出一個(gè)EmptyStackExceptin
異常要比后面拋出一個(gè)NullPointExceptin異常更好。
不要羞于傳遞異常
很多程序員都感覺應(yīng)該捕獲拋出的全部異常。如果調(diào)用了一個(gè)拋出異常的方法,例如FileInputStream構(gòu)造器或
readLine方法,這些方法就會(huì)本能地捕獲這些可能產(chǎn)生的異常。其實(shí),傳遞異常要比捕獲這些異常更好:
public void readStuff(String filenam) throws IOException{
InputStream in=new InputStream(filenam);
...
}
讓高層次的方法通知用戶發(fā)生了錯(cuò)誤,或者放棄不成功的命令更加適宜。
PS: 規(guī)則5,6可以歸納為”早拋出,晚捕獲”,拋出不能處理的異常,捕獲可以處理的異常
使用斷言
斷言的概念
假設(shè)確信某個(gè)屬性符合要求,并且代碼的執(zhí)行依賴于這個(gè)屬性。
斷言的關(guān)鍵字是assert,這個(gè)關(guān)鍵字有兩周形式:
assert 條件:和assert 條件:表達(dá)式;
這兩種形式都會(huì)對(duì)條件進(jìn)行檢測(cè),如果結(jié)果為false,則拋出一個(gè)AssertionError異常,在第二種形式中,
表達(dá)式將傳入AssertError的構(gòu)造器,并轉(zhuǎn)換成一個(gè)消息字符串
啟動(dòng)和禁用斷言
在默認(rèn)情況下,斷言被禁用??梢栽谶\(yùn)行程序時(shí)用-enableassertions或者-ea選項(xiàng)啟用。
命令是 java -enableassertions MyApp
需要注意的是,在啟用或禁用斷言時(shí)不必重新編譯程序。啟用或禁用斷言時(shí) 類加載器的功能,當(dāng)斷言被禁用時(shí),
類加載器將跳過斷言代碼,因此不會(huì)降低程序的運(yùn)行速度。
使用斷言完成參數(shù)檢查
什么時(shí)候應(yīng)該選擇使用斷言呢?請(qǐng)記住下面幾點(diǎn):
斷言失敗是致命的,不可恢復(fù)的錯(cuò)誤。
斷言檢查只用于開發(fā)和測(cè)試階段
記錄日志
基本日志
可以使用全局日志記錄器(global logger) 并調(diào)用其info方法。
Logger.getGlobal().info("日志測(cè)試");
設(shè)置日志級(jí)別:
Logger.getGlobal().setLevel(Level.INFO);
高級(jí)日志
可以自定義日志記錄器,可以調(diào)用getLogger方法創(chuàng)建或獲取記錄器:
private static final Logger myLogger=Logger.getLogger("com.jay.exception.LoggerTest");
未被任何變量引用的日志記錄器可能會(huì)被垃圾回收機(jī)制回收。為了防止這種情況發(fā)生,要像
上面的例子中一樣,用一個(gè)靜態(tài)變量存儲(chǔ)日志記錄的一個(gè)引用。
通常,有以下7個(gè)日志記錄器級(jí)別:
SERVER
WARNING
INFO
CONFIG
FINE
FINER
FINEST
默認(rèn)情況下,只記錄前三個(gè)級(jí)別。也可以設(shè)置其他的級(jí)別。例如:
logger.setLevel(Level.FINE)
修改日志管理器配置
在默認(rèn)情況下,配置文件存在于 jre/lib/logging.propertiest,要想使用另一個(gè)配置文件,
就要將 java.util.logging.config.file 特性設(shè)置為配置文件的存儲(chǔ)位置。并用下列命令啟動(dòng)應(yīng)用程序
java -Djava.util.logging.config.file=configFile MainClass
作者:碼農(nóng)飛哥
微信公眾號(hào):碼農(nóng)飛哥