【java】異常

love_Aym發表於2018-04-10

轉載:https://blog.csdn.net/weisg81/article/details/52139179

          https://blog.csdn.net/zhangliangzi/article/details/51290026


一、概述

在Java中,所有的事件都能由類描述,Java中的異常就是由java.lang包下的異常類描述的。

異常就是java程式在執行過程中出現的錯誤

二、類


1、Throwable(可丟擲):異常類的最終父類,它有兩個子類,Error與Exception。 
Throwable中常用方法有: 
getCause():返回丟擲異常的原因。
getMeage():返回異常的訊息資訊。 
printStackTrace():物件的堆疊跟蹤輸出至錯誤輸出流,作為欄位 System.err 的值。

2、Error(錯誤):表示程式無法處理的錯誤,一般與程式設計師的執行操作無關。理論上這些錯誤是不允許發生的,如果發生,也不應該試圖通過程式去處理,所以Error不是try-catch的處理物件,而JVM一般的處理方式是終止發生錯誤的執行緒。Error類常見子類有VirtualMachineError與AWTError。Error用來表示編譯時和系統錯誤,一般我們不用關心,這裡再重申一個概念,異常是發生錯誤時被丟擲的一個通知,所以Error是在編譯時和系統錯誤時被丟擲的異常。

3、Exception(異常):出現原因取決於程式,所以程式也理應通過try-catch處理。 
異常分為兩類:可查異常與不可查異常。

1)可檢查異常(編譯時異常):編譯器要求必須處理,否則不能通過編譯,使用try-catch捕獲或者throws丟擲。常見的可查異常有IOException(IO錯誤)及其子類EOFExcption(檔案已結束異常)、FileNotFound(檔案未找到異常)。

2)不可檢查異常(執行時異常):編譯期不會檢查,所以在程式中可不處理,但如果發生,會在執行時丟擲。所以這類異常要儘量避免!常見的不可查異常都是RuntimeException類及其子類。

    1’ NullPointerException:空指標異常。呼叫了不存在的物件或未經例項化或初始化的物件時會丟擲,如當試圖操作一個空物件(賦值為null)的屬性、方法時就會丟擲。

(例項化:通俗的理解就是為物件開闢空間,使其可在規定範圍內被呼叫。注意:User u;這只是一個物件宣告,並沒有進行例項化或初始化。 
初始化:就是把例項化後的物件中的基本資料型別欄位賦預設值或設定值,為非基本型別賦值null,對於static欄位只會初始化一次。)

    2’ ArithmeticException:算術條件異常。最常見的就是0作除數時會丟擲。

    3’ ClassNotFoundException:類未找到異常。在通過反射Class.forName(“類名”)來獲取類時,如果未找到則會丟擲異常。

    4’ ArrayIndexOutOfBoundsException:陣列索引越界異常。當試圖運算元組的索引值為負數或大於等於陣列大小時會丟擲。

    5’ NegativeArraySizeException:陣列長度為負值異常。一般在初始化陣列大小為負值時丟擲。

    6’ ArrayStoreException:陣列型別不匹配值異常。例如將一個Object陣列中加入一個Integer物件與一個String物件時,型別不匹配就會丟擲。

    7’ IllegalArgumentException:非法引數異常。會在使用Java類庫方法時傳入引數值越界時丟擲。

三、try-catch-final

1、try-catch-finally異常捕獲語句: 

    try中是可能發生異常的程式段;

    catch中依次編寫對應的異常處理器方法,當丟擲異常後,由執行時系統在棧中從當前位置開始依次回查方法,直到找到合適的異常處理方法,如果未找到,則執行finally或直接結束程式執行。

    finally :無論是否捕獲或處理異常,finally塊裡的語句都會被執行。 
注意(很重要,面試常問):當在try塊或catch塊中遇到return語句時,finally語句塊將在方法返回之前被執行。 
在以下4種特殊情況下,finally塊不會被執行: 
1)在finally語句塊中丟擲了異常且未處理。 
2)在前面的程式碼中用了System.exit()退出程式。 
3)程式所在的執行緒死亡。 

4)CPU出現異常被關閉。

2、當try捕獲到異常,catch語句塊裡有處理此異常的情況:在try語句塊中是按照順序來執行的,當執行到某一條語句出現異常時,程式將跳到catch語句塊,並與catch語句塊逐一匹配,找到與之對應的處理程式,其他的catch語句塊將不會被執行,而try語句塊中,出現異常之後的語句也不會被執行,catch語句塊執行完後,最後執行finally語句塊後的語句。


  • 監控區域-try

如果在方法內部丟擲了異常或者在方法內部呼叫的其他方法丟擲了異常,這方法將在丟擲異常後終止,如果不希望方法就此終止,那麼在方法內設定一個特殊的塊來捕獲異常,所以稱為try塊

try {
    // Code that might generate exceptions
}

  • 異常處理程式-catch

再次強調一下,不被檢查的異常編譯時不會強制讓我們捕獲,所以需要大家自己注意這些異常。丟擲的異常需要在某處得到處理,這個“地點”就是異常處理程式,而且在Java中,針對捕獲到的不同異常,有不同的處理程式:

try {
    // Code that might generate exceptions
} catch(Type1 t1){
    // Handle exceptions of type1 
} catch(Type2 t2){
    // Handle exceptions of type2 
} catch(Type3 t3){
    // Handle exceptions of type3 
}

當發生異常事時(監控區域丟擲異常),Java異常處理機制將負責搜尋catch中與異常型別相匹配的第一個處理程式,進入這個catch塊,也就是說只有匹配的catch字句才能執行,即便是下面還有匹配的型別(為什麼下面還會存在匹配的型別,因為類繼承原因,下面還存在父類的異常),也不會執行,具有唯一匹配性。Java的這種異常捕獲、處理的模式,可以很好的將正常的程式碼和出現問題時處理的程式碼分開,而不是混在一起。

  • finally

有一些程式碼,無論try中是否丟擲異常,它們都能得到執行,這就是finally字句的作用。

try {
    // Code that might generate exceptions
} catch(Type1 t1){
    // Handle exceptions of type1 
} catch(Type2 t2){
    // Handle exceptions of type2 
} catch(Type3 t3){
    // Handle exceptions of type3 
} finally {

}

無論放生了什麼,finally字句始終都會執行,即便是你在try或catch中加入了continue、break或者return。

千萬不要在finally寫 返回語句,因為finally的作用是為了釋放資源,是肯定會被執行的,如果寫了那麼try和catch的結果就會被改變

如果catch裡面有return語句,finally中的語句會被執行,是在finally語句之前執行。執行時只是建立了返回路徑,再執行finally,最後才徹底返回,return語句中的值不會被改變。

finally用來做什麼

Java中主要通過finally把資源恢復到它們的初始狀態,如:已開啟的檔案或網路連結等,總言之,就是與外界“世界”的某個開關。這裡有一個原則,就是在產生了一個必須被清理的物件之後,立即進入一個try-finally語句塊,為什麼會是在之後而不是把這個物件初始化也放進這個try中呢,因為finally總會執行,就會導致可能去做了釋放沒有被初始化的物件,這樣會出現不良的程式碼結構。

終止模式

Java對於異常的處理採取的是終止模式,一旦發生問題,程式將不能繼續執行,與之對應的是恢復模式,就是當異常丟擲時,程式能夠繼續執行,而不是終止。在Java中如果我們要使用恢復模式,就需要將try塊放在while迴圈中,直到滿意,但這明顯是不靠譜的,也是我們不提倡的。所以噹噹前方法終止時,我們只能在異常處理塊中使程式向不同的方向繼續執行,而具體向什麼方向,取決於具體的實現。


四、案例
public class Demo1_Exception {
	/**
	 * * A:JVM預設是如何處理異常的
		* main函式收到這個問題時,有兩種處理方式:
		* a:自己將該問題處理,然後繼續執行--trycatch
		* b:自己沒有針對的處理方式,只有交給呼叫main的jvm來處理--將異常列印到結果介面並終止程式
		* jvm有一個預設的異常處理機制,就將該異常進行處理. 並將該異常的名稱,異常的資訊.異常出現的位置列印在了控制檯上,同時將程式停止執行
	* B:案例演示
		* JVM預設如何處理異常
	 */
	public static void main(String[] args) {
		//demo1();
		Demo d = new Demo();
		int x = d.div(10, 0);
		System.out.println(x);
	}

	public static void demo1() {
		int[] arr = {11,22,33,44,55};
		//arr = null;			//NullPointerException :空指標異常
		System.out.println(arr[10]);	//ArrayIndexOutOfBoundsException :陣列索引越界異常
	}
}

class Demo {
	public int div(int a,int b) {		//a = 10,b = 0
		return a / b;			// 10 / 0  被除數是10,除數是0當除數是0的時候違背了算數運演算法則,丟擲異常
						//new ArithmeticException("/ by zero");
	}
} 
public class Demo2_Exception {
	/**
	 * * A:異常處理的兩種方式
			* a:try…catch…finally
				* try catch
				* try catch finally
				* try finally 
			* b:throws
	    * B:try...catch處理異常的基本格式
			* try…catch…finally
	    * C:案例演示
			* try...catch的方式處理1個異常		
		try:用來檢測異常的
		catch:用來捕獲異常的
		finally:釋放資源
		
		當通過trycatch將問題處理了,程式會繼續執行
	 */
	public static void main(String[] args) {
		Demo2 d = new Demo2();
		try{
			int x = d.div(10, 0);
			System.out.println(x);
		}catch(ArithmeticException a) {	   //出現異常時相當於:ArithmeticException a = new ArithmeticException();
			System.out.println("出錯了,除數為零了");
		}		
		System.out.println("1111111111111111");  //捕獲異常處理過後程式會繼續執行
	}
}

class Demo2 {
	public int div(int a,int b) {		//a = 10,b = 0
		return a / b;			// 10 / 0  被除數是10,除數是0當除數是0的時候違背了算數運演算法則,丟擲異常
						//new ArithmeticException("/ by zero");
	}
} 
public class Demo3_Exception {
	/**
	 * A:案例演示
	 * try...catch的方式處理多個異常
	 * JDK7以後處理多個異常的方式及注意事項
 
	 * try後面如果跟多個catch,那麼小的異常放前面,大的異常放後面,根據多型的原理,如果大的放前面,就會將所有的子類物件接收
	 * 後面的catch就沒有意義了
	 */
	public static void main(String[] args) {
		//demo1();
		int a = 10;
		int b = 0;
		int[] arr = {11,22,33,44,55};
		
		//JDK7如何處理多個異常
		try {
			System.out.println(a / b);
			System.out.println(arr[10]);
		} catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {   //不推薦,因為會掩蓋具體是哪個異常
			System.out.println("出錯了");
		} 
	}

	public static void demo1() {
		int a = 10;
		int b = 0;
		int[] arr = {11,22,33,44,55};
		
		try {
			System.out.println(a / b);
			System.out.println(arr[10]);
			arr = null;
			System.out.println(arr[0]);
		} catch (ArithmeticException e) {
			System.out.println("除數不能為零");
		} catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("索引越界了");
		} catch (Exception e) {		//Exception e = new NullPointerException();父類引用子類物件,多型機制
			System.out.println("出錯了");
		}		
		System.out.println("over");
	}
}
import java.io.FileInputStream;
public class Demo4_Exception {
	/**
	 * * A:編譯期異常和執行期異常的區別
		* Java中的異常被分為兩大類:編譯時異常和執行時異常。
		* 所有的RuntimeException類及其子類的例項被稱為執行時異常,其他的異常就是編譯時異常
		
		* 編譯時異常:Java程式必須顯示處理,否則程式就會發生錯誤,無法通過編譯
		* 執行時異常:無需顯示處理,也可以和編譯時異常一樣處理
	* B:案例演示
		* 編譯期異常和執行期異常的區別
		編譯時異常也叫做未雨綢繆異常,在做某些事情的時候要做某些準備
		編譯時異常:在編譯某個程式的時候,有可能會有這樣那樣的事情發生,比如檔案找不到,這樣的異常就必須在編譯的時候處理
		如果不處理編譯通不過
		
		執行時異常:就是程式設計師所犯得錯誤,需要回來修改程式碼
	 */
	public static void main(String[] args) {
		try {
			FileInputStream fis = new FileInputStream("xxx.txt");  //找不到檔案
		} catch(Exception e) {			
		}
	}
}
public class Demo5_Throwable {
	/**
	 * * A:Throwable的幾個常見方法
		* a:getMessage():獲取異常資訊,返回字串。
		* b:toString(): 獲取異常類名和異常資訊,返回字串。
		* c:printStackTrace():獲取異常類名和異常資訊,以及異常出現在程式中的位置。返回值void。   jvm預設就用這種方式處理異常!!!
	* B:案例演示
		* Throwable的幾個常見方法的基本使用
	 */
	public static void main(String[] args) {
		try {
			System.out.println(1/0);
		} catch (Exception e) {			//Exception e = new ArithmeticException("/ by zero");
			//System.out.println(e.getMessage());	//獲取異常資訊
			//System.out.println(e.toString()); 	//呼叫toString方法,列印異常類名和異常資訊,System.out.println(e); 效果一樣
			e.printStackTrace();		//jvm預設就用這種方式處理異常
		}
	}

}
public class Demo6_Exception {
	/**
	 * * A:throws的方式處理異常
		* 定義功能方法時,需要把出現的問題暴露出來讓呼叫者去處理,那麼就通過throws在方法上標識。
	   * B:案例演示
		* 舉例分別演示編譯時異常和執行時異常的丟擲
		* 編譯時異常的丟擲必須對其進行處理
		* 執行時異常的丟擲可以處理也可以不處理
	 * @throws Exception 
	 */
        public void setAge(int age) throws AgeOutOfBoundsException {  // 注意方法中如果是一個執行時異常就可以不在方法上宣告,如果是編譯時異常就必須在方法上上宣告
		if(age >0 && age <= 150) {
			this.age = age;
		}else {
			//Exception e = new Exception("年齡非法");
			//throw e;
			throw new AgeOutOfBoundsException("年齡非法");  //兩種寫法都可以~~
		}
	}  

  throws和throw的區別 

  • throws

        * 用在方法宣告後面,跟的是異常類名

        * 可以跟多個異常類名,用逗號隔開

        * 表示丟擲異常,由該方法的呼叫者來處理

  • throw

        * 用在方法體內,跟的是異常物件名

        * 只能丟擲一個異常物件名

        * 表示丟擲異常,由方法體內的語句處理

public class Demo7_Finally {
	/**
	 * * A:finally的特點
			* 被finally控制的語句體一定會執行
			* 特殊情況:在執行到finally之前jvm虛擬機器退出了(比如System.exit(0))
		* B:finally的作用
			* 用於釋放資源,在IO流操作和資料庫操作中會見到
		* C:案例演示
			* finally關鍵字的特點及作用
		*return語句相當於是方法的最後一口氣,那麼在他將死之前會看一看有沒有finally幫其完成遺願,如果有就將finally執行後在徹底返回
	 */
	public static void main(String[] args) {
		try {
			System.out.println(10/0);
		} catch (Exception e) {
			System.out.println("除數為零了");
			System.exit(0);								//退出jvm虛擬機器
			return;
		} finally {
			System.out.println("看看我執行了嗎");
		}
	}

}
public class Demo8_Exception {
	/**
	 * * A:為什麼需要自定義異常
	 	* 通過名字區分到底是神馬異常,有針對的解決辦法 
		* 舉例:人的年齡
	* B:自定義異常概述
		* 繼承自Exception
		* 繼承自RuntimeException
	* C:案例演示
		* 自定義異常的基本使用
	 */
	public static void main(String[] args) {

	}
}

class AgeOutOfBoundsException extends Exception {
	public AgeOutOfBoundsException() {
		super();		
	}
	public AgeOutOfBoundsException(String message) {
		super(message);		//底層傳到頂層就行
	}	
}

五、異常注意事項

 1、子類重寫父類方法時,子類的方法必須丟擲相同的異常或父類異常的子類。(父親壞了,兒子不能比父親更壞)

2、如果父類丟擲了多個異常,子類重寫父類時,只能丟擲相同的異常或者是他的子集,子類不能丟擲父類沒有的異常

3、如果被重寫的方法沒有異常丟擲,那麼子類的方法絕對不可以丟擲異常,如果子類方法內有異常發生,那麼子類只能try,不能throws

4、如何使用異常處理

    * 原則:如果該功能內部可以將問題處理,用try,如果處理不了,交由呼叫者處理,這是用throws

    * 區別:

        * 後續程式需要繼續執行就try

        * 後續程式不需要繼續執行就throws

5、如果JDK沒有提供對應的異常,需要自定義異常。