java異常機制
異常的基本概念
什麼是異常
-
在程序運行中出現的錯誤,稱爲異常
-
異常模擬的是現實世界中不正常的事件
-
java中採用類的方式實現異常(和其他的類是一樣的,按照類和對象理解就可以啦)
-
類是可以創建對象的
NullPointerException e = 0x1234;
-
e 是引用類型,e 中保存的內存地址指向堆中的異常對象
-
這個對象一定是
NullPointerException
類型的異常 -
e 表示真實存在的
NullPointerException
類型的異常“對象”異常都是一類一類的
-
-
異常舉例
ArithmeticException 算術異常(divide by zero)
ArrayIndexOutOfBoundsException 數組越界;
ArrayStoreException 向數組對象中存儲錯誤類型的數據
ClassCastException 類型轉換異常 是一類異常
ClassNotFoundException 找不到類異常
CloneNotSupportedException 克隆不支持異常
表示調用了Object類中的clone方法來創建一個對象,但是對象的類中沒有實現Cloneable接口
IndexOutOfBoundsException 越界異常
InstantiationException 實例化異常
嘗試創建一個不能實例化的類的異常,例如實例化了一個接口,抽象方法,數組類,原始數據類型;
NegativeArraySizeException 負的數組大小異常
嘗試創建負數長度的數組的異常;
NoSuchFieldException 訪問類中並不存在的成員拋出的異常;
NoSuchMethodException 訪問類中並不存在的方法拋出的異常;
NullPointerException 空指針異常;
NumberFormatException 在將字符串類型轉爲數字類型時,字符串格式不正確的異常;
SecurityException 操作不安全的異常;
StringIndexOutOfBoundsException 由String類中的方法拋出的“字符串越界異常”,一般是`charAt`方法引起的;
UnsupportedOperationException 請求的操作不支持的異常
RuntimeException 繼承自 Exception 類,它 和 它的子類都是 unchecked 異常;
Exception 的直接子類都是 checked 異常, 即編譯時異常,需要提前處理(throws 或者 try...catch...)
- 異常舉例
public class Test01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
int c = a/b;// ArithmeticException
// 上面的代碼出現了異常,“沒有處理”,下面的代碼不會執行,直接退出了 JVM
System.out.println(c);
}
}
/*控制檯輸出
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.test.exception.Test01.main(Test01.java:7)
*/
- 以上程序編譯通過了,但是運行時出現了異常,表示發生了某一個異常事件。
- 控制檯輸出信息的本質:
- 程序在執行過程中發生了ArithmeticException(算術異常)這個事件,JVM爲我們創建了一個ArithmeticException 類型的對象,並且這個對象中包含了詳細的異常信息,並且JVM將這個對象中的信息輸出到控制檯。
異常機制的作用
-
java 語言爲我們提供了一種完善的異常處理機制,作用是:
程序發生異常事件之後,爲我們輸出詳細的信息,通過這個信息,可以對程序進行一些處理,使得程序更加健壯。
異常的分類&異常繼承結構
異常繼承結構
異常的捕獲和處理
使用throws
關鍵字聲明拋出異常
-
在方法聲明的位置上使用 throws 關鍵字向上拋出異常
-
例如
import java.io.*; public class Test02 { public static void main(String[] args) { FileInputStream fs = new FileInputStream("c:\\ab.txt"); } }
- 以上程序編譯不通過
.\Test02.java:7: 錯誤: 未報告的異常錯誤FileNotFoundException; 必須對其進行捕獲或聲明以便拋出
FileInputStream fs = new FileInputStream("c:\\ab.txt");
// 爲啥編譯器會檢測出來這個個編譯時異常咧?
因爲FileInputStream 這個構造方法在聲明的位置上使用了 throws FileNotFoundException;
-
使用 throws 處理異常不是真正的處理異常,而是推卸責任 ;只是一直往上拋並沒有處理
import java.io.*; public class Test03 { public static void main(String[] args) throws FileNotFoundException { m1();// m1()方法的聲明位置上使用throws(向上拋) // 上面的m1方法如果出現了異常,因爲採用的是throws上拋給了JVM,JVM遇到這個異常就會退出JVM // 下面這行代碼就不會執行 System.out.println("hello world"); } static void m1() throws FileNotFoundException{ m2();// m2 方法聲明的位置上使用throws(向上拋) } static void m2() throws FileNotFoundException { m3();// m3() 方法的聲明位置上使用 throws(向上拋) } static void m3() throws FileNotFoundException { m4(); // m4方法的聲明位置上使用 throws(向上拋) } static void m4() throws FileNotFoundException{ new FileInputStream("c://daf"); // FileInputStream 構造方法聲明位置上使用throws(向上拋) } } /* * 在程序執行過程中出現了 FileNotFoundException類型的異常 * JVM爲我們創建了一個FileNotFoundException類型的對象 * 該對象中攜帶了以下信息,JVM負責將這些信息打印到控制檯。 * 並且JVM停掉了程序的運行。 Exception in thread "main" java.io.FileNotFoundException: c:\daf (系統找不到指定的文件。) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at com.test.exception.Test03.m4(Test03.java:20) at com.test.exception.Test03.m3(Test03.java:17) at com.test.exception.Test03.m2(Test03.java:14) at com.test.exception.Test03.m1(Test03.java:11) at com.test.exception.Test03.main(Test03.java:8) */
使用try-catch
捕獲處理異常
-
語法結構
try{ // 可能出現異常的代碼 }catch(異常類型1 變量){ // 處理異常的代碼 }catch(異常類型2 變量){ // 處理異常的代碼 }...
-
catch 語句塊可以寫多個
-
注意:catch語句塊中從上到下的異常類型必須是從小到大的
- 例如:
try {//可以編譯通過 FileInputStream fis = new FileInputStream("adnx.txt");//捕獲對應的FileNotFoundException異常 fis.read();//捕獲對應的IOException類型的異常 }catch(FileNotFoundException e) { //子異常 }catch(IOException e) { // 父異常 } try {//編譯報錯 FileInputStream fis = new FileInputStream("adnx.txt");//捕獲對應的FileNotFoundException異常 fis.read();//捕獲對應的IOException類型的異常 }catch(IOException e) {//父異常 }catch(FileNotFoundException e) {//子異常 }
-
try … catch …中最多執行一個 catch 語句塊。執行結束之後 try…catch… 就結束了
-
-
例子:
import java.io.*; public class Test05 { public static void main(String[] args) { try { // 程序執行到此處發生了FileNotFoundException類型的異常 // JVM 會自動創建一個FileNotFoundException 類型的對象,將該對象的內存地址賦值給catch出語句塊中的e變量 FileInputStream fis = new FileInputStream("abc"); // 上面代碼出現了異常,try語句塊的代碼不再繼續執行,直接進入catch語句塊中執行 fis.read(); }catch(FileNotFoundException e) { System.out.println("文件不存在異常"); // FileNotFoundException 類將Object類中的toString()方法重寫了。 System.out.println(e);//java.io.FileNotFoundException: abc (系統找不到指定的文件。) }catch(IOException e){ System.out.println("其它IO異常"); } } }
getMessage()
&& printStackTrace()
如何取得異常對象的具體信息,常用的方法有兩種:
-
取得異常描述信息
getMessage()
-
取得異常的堆棧信息(適用於程序調式階段)
printStackTrace()
舉例:
import java.io.*;
public class Test06 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("c:/adf.txt");
} catch (FileNotFoundException e) {
// 打印堆棧異常信息
//一般情況下都會使用該方法去調試程序
// e.printStackTrace();
// /*輸出如下:
// * java.io.FileNotFoundException: c:\adf.txt (系統找不到指定的文件。)
// at java.io.FileInputStream.open0(Native Method)
// at java.io.FileInputStream.open(Unknown Source)
// at java.io.FileInputStream.<init>(Unknown Source)
// at java.io.FileInputStream.<init>(Unknown Source)
// at com.test.exception.Test06.main(Test06.java:9)
// */
// System.out.println("abc");
// 在發生FileNotFoundException異常的時候,JVM爲我們執行了下面這行代碼,
//FileNotFoundException e = new FileNotFoundException("c:\\adf.txt (系統找不到指定的文件。)");
// Throwable 構造方法:
// public Throwable(String message) {
// fillInStackTrace();
// detailMessage = message;
// }
// 然後我們在調用e.getMessage()的時候會得到下面的輸出信息
String msg = e.getMessage();
System.out.println(msg);
//輸出 c:\adf.txt (系統找不到指定的文件。)
}
}
}
finally
語句塊詳解
-
finally
語句塊可以直接和 try 語句塊連用try ... finally...
-
try...catch...finally
也可以 -
在
finally
語句塊中的代碼一定會執行的。-
測試:
public class Test07 { public static void main(String[] args) { try { System.out.println("Abc"); return; } finally { System.out.println("hhhh"); } } } // 最終輸出 Abc hhhh
可以這麼理解程序的執行順序:
先執行第 4 行, 再執行第 7 行 最後再執行第 5 行
-
再測試
import java.io.*; public class Test08 { public static void main(String[] args) throws FileNotFoundException{ try { FileInputStream fis = new FileInputStream("c:/ab.txt"); // 下面這句不會執行 System.out.println("aaaaa"); } finally { // 下面這句會執行 System.out.println("hhhhh"); } } } /* 輸出結構 hhhhh Exception in thread "main" java.io.FileNotFoundException: c:\ab.txt (系統找不到指定的文件。) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at com.test.exception.Test08.main(Test08.java:8) */
可以這麼理解程序的執行順序:
執行到第 6 行出現異常,先執行第 11 行, 再向上拋出異常,JVM打印出異常信息
-
再測試(注意)
- 只有這一種情況下,finally 語句塊中的代碼不會被執行
- 在執行 finally 語句塊之前退出 JVM,則 finally語句塊不會被執行
public class Test09 { public static void main(String[] args) { try { // 退出jvm System.exit(0); } finally { // 什麼都不會輸出 System.out.println("hhhh"); } } }
- 只有這一種情況下,finally 語句塊中的代碼不會被執行
-
深入 finally
語句塊(面試常考)
- 舉例01
public class Test10 {
public static void main(String[] args) {
int i = m1();
System.out.println(i); //10
}
private static int m1() {
int i = 10;
try {
return i;
} finally {
i++;
System.out.println("m()->i: " + i);//11
}
}
}
-
以上代碼的輸出結果爲:
m()->i: 11 10
以上代碼的執行原理:
public class Test10 { public static void main(String[] args) { int i = m1(); System.out.println(i);//10 } private static int m1() { int i = 10; try { int temp = i; //jvm定義一個臨時變量temp 存儲 i, 然後return的是臨時變量的值 return temp;// 所以返回的10 } finally { i++;// 針對i操作 System.out.println("m()->i: " + i);//11 } } }
-
02
finally
語句塊是一般來說是一定會執行的,所以通常在程序中爲了保證某資源一定會被釋放,一般寫在finally
語句塊中釋放資源import java.io.*; public class Test11 { public static void main(String[] args) { // 必須在外邊聲明,否則finally語句塊中找不到fis FileInputStream fis = null; try { fis = new FileInputStream("Test11.java"); } catch (FileNotFoundException e) { e.printStackTrace(); }finally { // 爲了保證資源一定會釋放 if(fis != null) { try { fis.close();// 釋放資源 }catch(IOException e) {// 捕獲異常 e.printStackTrace(); } } } } }
final
&& finalize
&& finally
區別
三者沒有任何聯繫
final
- 修飾的類不能被繼承
- 修飾的方法不能被重寫Override
- 修飾的實例變量要手動賦值-且不能再修改
- 修飾的靜態變量是最終的
finalize
- 是
Object
類中的方法名- 垃圾回收器在回收垃圾之前會調用該方法
- 一般用於在回收前釋放資源
- 垃圾回收器在回收垃圾之前會調用該方法
- 是
finally
- 是異常機制中用於聲明最終執行的代碼塊的關鍵字
finally
語句塊中的代碼總是執行,無論是否發生異常- 但是在try中如果執行了
System.exit(0)
,那麼 jvm 就會退出,這個時候finally
語句塊中的代碼就不會被執行了。
- 但是在try中如果執行了
自定義異常 &手動拋出異常
-
假設有如下業務:
- 用戶在註冊的時候要求用戶名要超過6位,否則就拋出 IllegalNameException 異常
-
自定義異常
自定義異常既可以定義編譯時異常又可以定義運行時異常
- 定義編譯時異常直接繼承
Exception
- 定義運行時異常直接繼承
RuntimeException
- 定義編譯時異常直接繼承
-
實現
public class Test12 { public static void main(String[] args) { CustomeService cs = new CustomeService(); try { cs.register("jack");// 需要處理異常(要麼拋出,要麼捕獲) // jack 小於 6個字符,所以 /* com.test.exception.IllegalNameException: 用戶名長度不能低於6個字符 at com.test.exception.CustomeService.register(Test12.java:27) at com.test.exception.Test12.main(Test12.java:7) */ } catch (IllegalNameException e) { e.printStackTrace(); } } } class IllegalNameException extends Exception{//定義編譯時異常 //定義異常一般提供兩個構造方法 public IllegalNameException() { } public IllegalNameException(String msg) { super(msg); } } class CustomeService{ public void register(String name) throws IllegalNameException{ if(name.length() < 6) { IllegalNameException e = new IllegalNameException("用戶名長度不能低於6個字符"); throw e; //手動拋出異常//這裏不使用 try...catch... 處理,因爲是要讓用戶知道該異常,所以需要向上拋出 }else { //程序能執行到這裏,證明用戶名是合法的 System.out.println("註冊成功"); } } }
-
自定義異常
-
可以定義編譯時異常或者運行時異常
- 根據異常出現的概率來決定
- 然後就可以確定繼承那個類了
- 繼承
Exception
是定義編譯時異常 - 繼承
RuntimeException
是運行時異常
- 繼承
-
定義異常的時候一般只要寫兩個構造方法
public IllegalNameException() {} public IllegalNameException(String msg) { super(msg);// 可以用於打印e.message }
-
自定義的異常一般在服務端都是向上拋出
- throws
-
-
方法的覆蓋與異常
定律:重寫的方法不能比被重寫的方法拋出更寬泛的異常
- 子類無法拋出比父類更多的異常
class A{
public static void m1(){
}
}
class B extends A{
@Override
public static void m1() throws Exception{// 編譯無法通過
// 子類不能拋出比父類更多的異常
}
}
-
子類無法拋出比父類更高層次的異常
import java.io.*; class A{ public static void m1() throws FileNotFoundException{ } } class B extends A{ @Override public static void m1() throws IOException{// 編譯無法通過 // 子類不能拋出比父類更高層次的異常 } }
import java.io.*; class A{ public static void m1() throws IoException{ } } class B extends A{ @Override public static void m1() throws FileNotFoundException{// 編譯可以通過 // 子類可以拋出比父類更低層次的異常 } }
以上