1. 程式人生 > >Java 知識點整理-13.異常

Java 知識點整理-13.異常

目錄

異常的概述:

異常的分類:

異常的繼承體系:

JVM預設是如何處理異常的:

異常處理的兩種方式:

處理異常的方式一 try...catch

編譯期異常和執行期異常的區別

Throwable的幾個常見方法

處理異常的方式二 throws

throw的概述:

throws和throw的區別:

finally的特點:

finally的作用:

finally關鍵字的面試題

自定義異常概述和基本使用:

異常注意事項

如何使用異常處理

異常練習


異常的概述:

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


異常的分類:

Throwable概述:public class Throwable extends Object implements Serializable,Throwable類是Java語言中所有錯誤和異常的超類(父類)。只有當物件是此類(或其子類之一)的例項時,才能通過Java虛擬機器或者Java throw語句丟擲。類似地,只有這個類或其子類之一才可以是catch子句中的引數型別。

兩個子類的例項,Error和Exception,通常用於指示發生了異常情況。 通常,這些例項是在異常情況的上下文中新近建立的,因此包括了相關的資訊(比如堆疊跟蹤資料)。

Throwable包含其執行緒建立時執行緒執行堆疊的快照。它還包含了給出有關錯誤更多訊息的訊息字串。

java.lang包下,使用無需導包。

兩個直接子類:Error (伺服器宕機,資料庫崩潰等);Exception。


異常的繼承體系:

運作時異常,一般都屬於程式設計師犯的錯誤。編譯時異常,編譯時不處理,編譯不通過。


JVM預設是如何處理異常的:

main函式收到這個問題時,有兩種處理方式:

ⅰ.自己將該問題處理,然後繼續執行。

ⅱ.自己沒有針對的處理方式,只有交給呼叫main的jvm來處理。

jvm有一個預設的異常處理機制,就是將該異常進行處理。並將該異常的名稱、異常的資訊,異常出現的位置列印在了控制檯上,同時將程式停止執行。

案例演示:JVM預設如何處理異常。

public class Demo1_Exception {
	public static void main(String[] args) {
//		demp1();
		Demo d = new Demo();
		int x = d.div(10, 0);			//ArithmeticException  把異常物件賦值給x,x接不住,主方法裡出現問題。main()自己並未進行處理,交給呼叫者虛擬機器進行處理,虛擬機器列印對應錯誤。 看錯誤,從後往前看。
		System.out.println(x);			
	}

	public static void demp1() {
		int[] arr = {11,22,33,44,55};
//		arr = null;
		
//		System.out.println(arr);		//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的時候違背了算數運演算法則,丟擲ArithmeticException 異常以物件的形式丟擲。new ArithmeticException("/ by zero");
	}
}

ArithmeticException概述:

public class ArithmeticException extends RuntimeException,當出現異常的運算條件時,丟擲此異常。例如,一個整數“除以零”時,丟擲此類的一個例項。

 


異常處理的兩種方式:

兩種方式:

Ⅰ.try catch finally,try是用來檢測異常的,catch是用來捕獲異常的,finally是用來釋放資源的。

助記:世界上最真情的相依就是你在try我在catch,無論你發什麼脾氣,我都靜靜接受,默默處理。當通過try chtch將問題處理後,程式可以繼續執行。

三種搭配:ⅰ.try catch ⅱ.try catch finally ⅲ.try finally 

Ⅱ.throws

try...catch處理異常的基本格式:

try…catch…finally

案例演示:try...catch的方式處理1個異常。

public class Demo2_Exception {
	public static void main(String[] args) {
		Demo2 d = new Demo2();
		try {
			int x = d.div(10, 0);			//ArithmeticException
			System.out.println(x);
		}catch(ArithmeticException a) {		//x接不住的異常物件被a接住了。ArithmeticException a = new ArithmeticException();
			System.out.println("錯誤!除數為零!");
		}
		
		System.out.println("try catch將異常處理後,後續程式碼可以繼續執行。否則,不可以。");
	}
}

class Demo2 {
	public int div(int a, int b) {
		return a / b;
	}
}

處理異常的方式一 try...catch

案例演示:try...catch的方式處理多個異常。

public class Demo3_Exception {
	public static void main(String[] args) {
		int a = 10;
		int b = 0;
		int[] arr = {11,22,33,44,55};
		
		try {
			System.out.println(a / b);				//try中出現異常後的程式碼不會被執行。
			System.out.println(arr[10]);
			arr = null;
			System.out.println(arr[0]);
		}catch(ArithmeticException e) {
			System.out.println("除數不能為0!");
		}catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("索引越界!");
		}catch(Exception e) {						//Exception e = new NUllPointerException();父類引用指向子類物件。最好抓與實際出錯匹配的異常,但開發中都是直接抓Exception。多個catch時Exception必須放最後。因為Exception能處理所有子類所處理的異常,Exception之後的catch沒有意義。
			System.out.println("出錯了!");
		}
		
		System.out.println("over");					//try catch後的程式碼可以繼續執行。
	}

總結:try後面如果跟多個catch,那麼小的異常放前面,大的異常放後面。根據多型的原理,如果大的放前面,就會將所有的子類物件接收,後面的catch就沒有意義了。

案例演示:JDK7以後處理多個異常的方式及注意事項。

public class Demo3_Exception {
	public static void main(String[] args) {
		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("出錯了!");
		}
	}
}

安卓和JavaeEE處理異常方式的區別:

安卓客戶端開發,直接try{}catch(Exception e){}。JavaEE服務端開發,一般都是底層開發,出現異常從底層向上拋到最頂層,錯誤日誌列印內容。程式設計師根據錯誤日誌處理異常。


編譯期異常和執行期異常的區別

Java中的異常被分為兩大類:

編譯時異常和執行時異常。

所有的RuntimeException類及其子類的例項被稱為執行時異常,其他的異常就是編譯時異常。

編譯時異常:

Java程式必須顯示處理,否則程式就會發生錯誤,無法通過編譯。助記:編譯時異常屬於未雨綢繆型,在編譯某個程式時,要提前做某些準備,以免異常情況的發生。如果對這些情況不提前做處理,編譯通不過。

執行時異常:

Java程式無需顯示處理,也可以和編譯時異常一樣處理。當異常發生後,需要程式設計師回來修改程式碼。

總結:

執行時異常是程式設計師犯得錯誤,編譯時異常是在編譯時提前做準備防止錯誤的發生。

案例演示:編譯期異常和執行期異常的區別。

import java.io.FileInputStream;

public class Demo4_Exception {
	public static void main(String[] args) {
		try {
			FileInputStream fis = new FileInputStream("xxx.txt");		//編異時異常,不處理,編譯不通過。 檔案輸入流,從硬碟上讀取檔案,但讀取的檔案可能不存在。讀取不存在的檔案就會出錯,所以報編譯時異常。
		} catch (Exception e) {
			e.printStackTrace();
		}		
	}
}

Throwable的幾個常見方法

getMessage() 獲取異常資訊,返回字串。

toString() 獲取異常類名和異常資訊,返回字串。

printStackTrace() 獲取異常類名和異常資訊,以及異常出現在程式中的位置。返回值void。

案例演示 Throwable的幾個常見方法的基本使用。

public class Demo5_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(),列印異常類名和異常資訊。
			e.printStackTrace();					//列印異常類名、異常資訊,異常出現的位置。jvm的預設處理方式。
		}
	}
}

處理異常的方式二 throws

定義功能方法時,需要把出現的問題暴露出來讓呼叫者去處理。那麼就通過throws在方法上標識。

案例演示:舉例分別演示編譯時異常和執行時異常的丟擲。

public class Demo6_Exception {
	public static void main(String[] args) /*throws RuntimeException*/ {	//如果丟擲的是編譯時異常,在方法上就必須要做宣告,否則報錯。
		Person p = new Person();
		p.setAge(-17);
		System.out.println(p.getAge());
	}
}

class Person {
	private String name;
	private int age;
	public Person() {
		super();
		
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) /*throws RuntimeException*/ { //RuntimeException執行時異常,編譯時可以不處理。所以方法上不丟擲也是可以的。
		if(age > 0 && age <= 150) {
			this.age = age;
		}else {
			throw new RuntimeException("年齡非法!");
		}
	}
}

總結:編譯時異常的丟擲必須對其進行處理,方法上必須宣告丟擲異常。執行時異常的丟擲可以處理也可以不處理,方法上可以不宣告丟擲異常。


throw的概述:

在功能方法內部出現某種情況,程式不能繼續執行,需要進行跳轉時,就用throw把異常物件丟擲。


throws和throw的區別:

throws:

用在方法聲明後面,跟的是異常名。

可以跟多個異常名,用逗號隔開,無先後順序。

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

throw:

用在方法體內,跟的是異常物件名。舉例:丟擲一個匿名物件:throw new Exception("提示資訊"); 丟擲一個有名物件:Exception e = new Exception("提示資訊"); throw e;

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

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


finally的特點:

被finally控制的語句體一定會執行。

特殊情況:

在執行到finally之前jvm退出了(比如呼叫了System.exit(0))。

finally的作用:

用於釋放資源,在IO流操作和資料庫操作中會見到。

案例演示:finally關鍵字的特點及作用。

public class Demo7_Finally {
	public static void main(String[] args) {
		try {
			System.out.println(10 / 0);
		} catch (Exception e) {
			System.out.println("除數為零!");
//			System.exit(0); 					//退出jvm虛擬機器,則finally不執行。
			return;								
		} finally {
			System.out.println("程式繼續執行!");	//return執行了,finally也執行了。如果有finally,return在結束程式之前會先執行finally中的語句。
		}
	}
}

助記:return語句相當於方法的最後一口氣,那麼在他將死之前,會看一看有沒有finally,幫其完成遺願。如果有就將finally執行後再徹底返回。


finally關鍵字的面試題

面試題1:final,finally和finalize的區別。

final修飾類,不能被繼承。修飾方法,不能被重寫。修飾變數,只能賦值一次。

finally是try語句中的一個語句體,不能單獨使用,用來釋放資源

finalize概述:Object的成員方法,protected void finalize() 當垃圾回收器確定不存在該物件的更多引用時,由物件的垃圾回收器呼叫此方法。

面試題2:如果catch裡面有return語句,請問finally的程式碼還會執行嗎?如果會,請問是在return前還是return後。會,return先建立返回路徑,再執行finally,最後return再徹底返回。

演示:

public class Test1 {
	public static void main(String[] args) {
		Demo d = new Demo();
		System.out.println(d.method());
	}
}

class Demo {
	public int method() {
		int x = 10;
		try {
			x = 20;
			System.out.println(1 / 0);
			return x;	//try和catch可能走其一,所以都要寫return語句。
		} catch (Exception e) {
			x = 30;
			return x;	//ruturn執行了,建立出一個返回路徑。將30用返回路徑打包裝到了一個箱子裡,再看自己有沒有finally語句。如果有,執行finally語句。雖然x的值改為40,但返回路徑中的值並沒有變化。
		} finally {						
			x = 40;		//這句話沒有意義。finally應該寫釋放資源的程式碼。 千萬不要在finally中寫return語句,一旦這麼寫,try和catch中的return語句都沒有意義。try和catch中建立任何的返回路徑,finally中的return語句都會對其進行覆蓋
		}
	}
}

注意:千萬不要在finally裡面寫返回語句,因為finally的作用是為了釋放資源,是肯定會執行的。如果在這裡面寫返回語句,那麼try和catch的結果都會被改變,所以這麼寫就是犯罪啊。


自定義異常概述和基本使用:

為什麼需要自定義異常:

自定義異常就為了通過名字區分具體異常,利於排錯,有針對的進行處理。但具體實現全交給父類(Throwable,OBject)做了。

異常資訊由子類Exception通過有參構造傳到父類Throwable,父類將其儲存在成員變數detailMessage中,再通過getMessage()將其返回。自定義異常類重寫父類Exception中的有參構造即可。快捷鍵:Alt + Shift + C,重寫父類中的方法。

自定義異常概述:

Ⅰ.繼承自Exception。Ⅱ.繼承自RuntimeException,拋RuntimeException的子類,方法上可以不做宣告。二者區別:編譯時是否要處理,前者需要,後者可以不用。

案例演示:自定義異常的基本使用。

class AgeOutOfBoundsException extends Exception {
	public AgeOutOfBoundsException() {
		super();
	}

	public AgeOutOfBoundsException(String message) {
		super(message);
	}
}

異常注意事項

子類重寫父類方法時,子類的方法必須丟擲相同的異常或父類異常的子類。(父親壞了,兒子不能比父親更壞)。父子一致,兒子的異常可以小一些。

如果父類丟擲了多個異常,子類重寫父類時,只能丟擲相同的異常或者是他的子集。子類不能丟擲父類沒有的異常,且子類丟擲異常的數量<=父類丟擲異常的數量。不能瞎拋,不能多拋。

如果被重寫的方法沒有異常丟擲,那麼子類的方法絕對不可以丟擲異常;如果子類方法內有異常發生,那麼子類只能try,不能throws。父類沒拋,子類壞了也不能拋。


如何使用異常處理

原則:

如果該功能內部可以將問題處理,用try;如果處理不了,交由呼叫者處理,就用throws

區別:

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

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

如果JDK沒有提供對應的異常,需要自定義異常,裡面只寫幾個構造即可,其他方法直接用父類Throwable的。


異常練習

鍵盤錄入一個int型別的整數,對其求二進位制表現形式。

要求:

Ⅰ.如果錄入的整數過大,給予提示,錄入的整數過大請重新錄入一個整數BigInteger。

Ⅱ.如果錄入的是小數,給予提示,錄入的是小數,請重新錄入一個整數。

Ⅲ.如果錄入的是其他字元,給予提示,錄入的是非法字元,請重新錄入一個整數。

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Scanner;

/**
 * 鍵盤錄入一個int型別的整數,對其求二進位制表現形式。
 * 要求:
 * Ⅰ.如果錄入的整數過大,給予提示,錄入的整數過大請重新錄入一個整數BigInteger。
 * Ⅱ.如果錄入的是小數,給予提示,錄入的是小數,請重新錄入一個整數。
 * Ⅲ.如果錄入的是其他字元,給予提示,錄入的是非法字元,請重新錄入一個整數。
 * 分析:
 * 1.建立鍵盤錄入物件。
 * 2.將鍵盤錄入的結果儲存在String型別的字串中。儲存int型別中,如果有不符合條件的錄入,直接報錯,無法進行後續判斷。
 * 3.鍵盤錄入的結果轉換成int型別的資料,判斷是否正確。
 * 4.正確則直接轉換。
 * 5.錯誤則根據要求進行對應判斷。
 */
public class Test2 {
	public static void main(String[] args) {
		//1.建立鍵盤錄入物件。
		Scanner sc = new Scanner(System.in);
		System.out.println("請輸入一個整數:");
		while(true) {
			//2.將鍵盤錄入的結果儲存在String型別的字串中。儲存int型別中,如果有不符合條件的錄入,直接報錯,無法進行後續判斷。
			String line = sc.nextLine();
			//3.鍵盤錄入的結果轉換成int型別的資料,並判斷是否正確。
			try {
				int num = Integer.parseInt(line);
				//4.正確則直接轉換。
				System.out.println(Integer.toBinaryString(num));
				break;
			} catch (Exception e) {
				//5.錯誤則根據要求進行對應判斷。
				try {
					new BigInteger(line);
					System.out.println("錄入錯誤!整數過大!請重新輸入一個整數:");
				} catch (Exception e2) {
					try {
						new BigDecimal(line);
						System.out.println("錄入錯誤!錄入小數!請重新輸入一個整數:");
					} catch (Exception e1) {
						System.out.println("錄入錯誤!非法字元!請重新輸入一個整數:");
					}
				}
			}
		}
	}
}

快捷鍵:選中要加try catch的內容 按Alt + shift + Z (快速生成try catch)。