Java 知識點整理-13.異常
目錄
異常的概述:
異常就是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)。