1. 異常的體系結構
定義:異常就是有異於常態,和正常情況不一樣,有錯誤出現。在java中,將程序執行過程中的不正常的情況稱之為異常,開發過程中的語法錯誤和邏輯錯誤不是異常,發生異常時java會阻止當前方法或作用域的情況。
異常的體系結構
- Throwable:是java中所有異常和錯誤的超類,其兩個子類為 Error(錯誤) 和 Exception(異常)
- Error: 是程序中無法處理的錯誤,表示運行應用程序中出現了嚴重的錯誤。此類錯誤一般表示代碼運行時JVM出現問題。通常有Virtual MachineError(虛擬機運行錯誤)、NoClassDefFoundError(類定義錯誤)等。比如說當jvm耗完可用內存時,將出現OutOfMemoryError。此類錯誤發生時,JVM將終止線程。非代碼性錯誤。因此,當此類錯誤發生時,應用不應該去處理此類錯誤。
- Exception: 是程序本身可以捕獲並且可以處理的異常。其中可分為運行時異常(RuntimeException)和非運行時異常,也叫做受檢異常
- 運行時異常(不受檢異常): RuntimeException類極其子類表示JVM在運行期間可能出現的錯誤。編譯器不會檢查此類異常,並且不要求處理異常,比如用空值對象的引用(NullPointerException)、數組下標越界(ArrayIndexOutBoundException)。此類異常屬於不可查異常,一般是由程序邏輯錯誤引起的,在程序中可以選擇捕獲處理,也可以不處理。
- 非運行時異常(受檢異常): Exception中除RuntimeException極其子類之外的異常。編譯器會檢查此類異常,如果程序中出現此類異常,比如說IOException,必須對該異常進行處理,要么使用try-catch捕獲,要么使用throws語句拋出,否則編譯不通過。
從程序執行的過程來看編譯時異常和運行時異常
- 編譯時異常:程序在編譯時發生的異常(javac 源文件名.java)
- 運行時異常: 程序在運行時發生的異常(java 字節碼文件名)
2. 常見的異常
2.1. 運行時異常
- NullPointerException (空指針異常):指針指向的對象為空(null)
- ArrayIndexOutOfBoundException (數組角標越界異常) StringIndexOutOfBoundException (字符串越界異常) ...
- ClassCastException (類型轉換異常)
2.2. 編譯時異常 (編譯時異常必須進行處理否則無法運行)
- IOException
- FileNotFoundException
- ClassNotFoundException
2.3. 常見異常的運行Demo
package com.broky.exception.commonException;
import java.util.Scanner;
public class CommonEx {
static void ArithmeticExceptionDemo(){
int a = 10;
int b = 0;
int c = a / b;
/*Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.broky.exception.ArithmeticEx.main(ArithmeticEx.java:7)*/
}
static void ClassCastExceptionDemo(){
Object obj = new Double(1);
String str = (String)obj;
/*Exception in thread "main" java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.String (java.lang.Double and java.lang.String are in module java.base of loader 'bootstrap')
at com.broky.exception.ClassCastEx.main(ClassCastEx.java:7)*/
}
static void InputMismatchExceptionDemo(){
Scanner scan = new Scanner(System.in);
int num = scan.nextInt();
System.out.println(num);
/*asd
Exception in thread "main" java.util.InputMismatchException
at java.base/java.util.Scanner.throwFor(Scanner.java:939)
at java.base/java.util.Scanner.next(Scanner.java:1594)
at java.base/java.util.Scanner.nextInt(Scanner.java:2258)
at java.base/java.util.Scanner.nextInt(Scanner.java:2212)
at com.broky.exception.InputMismatchEx.main(InputMismatchEx.java:8)*/
}
static void NullPointerExceptionDemo(){
int[] arr = null;
System.out.println(arr[3]);
/*Exception in thread "main" java.lang.NullPointerException: Cannot load from int array because "arr" is null
at com.broky.exception.NullPointerEx.main(NullPointerEx.java:6)*/
}
static void NumberFormatExceptionDemo(){
String str = "abc";
int a = Integer.parseInt(str);
/*Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:660)
at java.base/java.lang.Integer.parseInt(Integer.java:778)
at com.broky.exception.NumberMismatchEx.main(NumberMismatchEx.java:6)*/
}
static void ArrayIndexOutOfBoundExceptionDemo(){
int[] arr = new int[3];
System.out.println(arr[3]);
/*Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at com.broky.exception.XIndexOutOfBoundEx.main(XIndexOutOfBoundEx.java:6)*/
String str = "abc";
System.out.println(str.charAt(3));
/*Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 3
at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)
at java.base/java.lang.String.charAt(String.java:711)
at com.broky.exception.XIndexOutOfBoundEx.main(XIndexOutOfBoundEx.java:11)*/
}
public static void main(String[] args) {
ArrayIndexOutOfBoundExceptionDemo();
}
}
3. 異常的抓拋模型原理
- 異常的拋出:如果程序在運行過程中執行某段代碼時發生了異常,那么系統(JVM)將會根據異常的類型,在異常代碼處創建對應的異常類型的對象並拋出,拋出給程序的調用者。一旦拋出對象以后,其后的代碼不再運行,程序終止.
異常的拋出分為:① 系統向外拋出異常 ② 手動向外拋出異常(throw) - 異常的抓取:異常的抓取可以理解為異常的處理方式, 取有 try-catch-finally 和 throws 兩種方式(詳情見異常的處理部分)
4. 異常的處理
4.1. try - catch - finally
- 將可能出現異常的代碼放到try{}中, 運行時, 如果代碼發成了異常的話,就會生成一個對應的異常類的對象.
這個產生的異常對象會與catch的異常類型相匹配,匹配成功后就被 catch 捕獲,然后運行catch{}中的代碼, 一般catch中的代碼為處理異常的代碼, 比如返回異常的信息和返回異常的詳細信息等. 一旦異常處理完成,就會跳出當前的try-catch結構
無論有沒有發生異常finally中的代碼都會最后被執行
- catch多個異常類型的時候, 如果有子父類關系,小的范圍寫上面大的范圍寫下面; 如果沒有子父類關系,誰在上誰在下無所謂.
- 常用的異常對象處理方式: ① e.getMessage() ② e.printSrackTrace()
- 在try結構中生命的變量,在出了try結構以后,就不能在被調用。如果想避免這種情況,就需要在try之前聲明變量並初始化,在try中賦值。
- 如果finally里面有return那么返回的一定是finally里面的
- try-catch-finally結構可以相互嵌套
- 體會1:使用try-catch-finally處理編譯時異常,是讓程序在編譯時就不再報錯,但是運行時仍然有可能報錯。相當於我們使用try-catch將一個編譯時可能出現的異常,延遲到運行時出現。p376
- 體會2:在開發中,運行時異常比較常見,此時一般不用try-catch去處理,因為處理和不處理都是一個報錯,最好辦法是去修改代碼。針對編譯時異常,我們一定要考慮異常處理。
package com.broky.exception.demo02;
public class TryCatchFinally {
public static void main(String[] args) {
String str = "abc";
try {
int i = Integer.parseInt(str);
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
System.out.println("運行完畢");
}
}
}
4.2. throws + 異常類型
-
throws 一般用於方法中可能存在異常時, 需要將異常情況向方法之上的層級拋出, 由拋出方法的上一級來解決這個問題, 如果方法的上一級無法解決的會就會再將異常向上拋出, 最終會拋給main方法. 這樣一來main方法中調用了這個方法的時候,就需要解決這個可能出現的異常.
當然main方法也可以不解決異常, 將異常往上拋出給java虛擬機, 如果java虛擬機也無法解決的話,那么java虛擬機就over了 -
throws + 異常類型
寫在方法的聲明處.指明此方法執行時,可能會拋出的異常類型.
一旦方法體執行時,出現異常,仍會在異常代碼處生成一個異常類的對象,此對象滿足throws后異常類型時,就會被拋出。
異常代碼的后續的代碼,就不再執行! -
體會:tyr-catch-fianlly:真正的將異常給處理掉了。throws的方式只是將異常拋給了方法的調用者,並沒有真正將異常處理掉。
注意: 將異常向上拋出也算是一種處理異常的方法
package com.broky.exception;
import java.io.FileNotFoundException;
public class ThrowsEx {
public void setAge2(int age) throws FileNotFoundException {
if (age < 0) {
throw new FileNotFoundException("輸入的年齡小於0");
}
}
public void TestsetAge2() throws FileNotFoundException {
setAge2(2);
}
public static void main(String[] args) {
try {
ThrowsEx.throwsExTest();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
5. 重寫方法異常拋出的規則
- 子類重寫的方法拋出的異常類型不大於父類被重寫的方法拋出的異常類型
下面代碼中,main方法中向display方法中傳入了SuperClass的子類對象.那么到display方法中調用s.method調用的就是SpuerClass的子類SubClass重寫的method方法, 如果這個方法拋出的異常范圍大於父類SuperClass所拋出的異常的話,那么在display方法中對異常的catch處理就會catch不到這個異常.
package com.broky.exception;
import java.io.FileNotFoundException;
import java.io.IOException;
public class OverrideTest {
public void display(SuperClass s) {
try {
s.method();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
OverrideTest overrideTest = new OverrideTest();
overrideTest.display(new SubClass());
}
}
class SuperClass {
public void method() throws IOException {
System.out.println("super");
}
}
class SubClass extends SuperClass {
public void method() throws FileNotFoundException {
System.out.println("sub");
}
}
6. 開發中如何選擇使用try-catch-finally 還是throws?
- 如果分類中被重寫的方法沒有throws方式處理異常,則子類重寫的方法也不能使用throws,意味着如果子類重寫的方法中有異常,必須使用tyr-catch-finally方式處理.
- 執行的方法中,先后又調用了另外的幾個方法,這幾個方法是遞進關系執行的.我們建議這幾個方法使用throws的方式處理.而執行的方法a可以考慮使用try-catch-finally方式進行處理.
因為如果在a方法要調用d方法時,如果在b方法內try-catch,當b方法異常時,並不會給方法a返回所需要的數據.因此最好使用throws將異常集合到一塊再處理. - 注意: try-catch和throws再方法中不要同時使用,因為只要使用try-catch就已經將異常處理掉了,再throws沒有任何意義.
7. 手動拋出異常 throw
手動拋出的異常有兩種, 分別為運行時異常和編譯時異常.
拋出運行時異常時, 可以不用處理拋出的這個異常.
拋出編譯時異常時, 必須要處理拋出的這個異常.
詳細解析請看下面的代碼
package com.broky.exception;
/*Demo
手動拋出異常
自己throw(制造一個異常拋出)
*/
import java.io.FileNotFoundException;
public class ThrowEx {
// 手動拋出運行時異常
public void setAge(int age) {
if (age < 0) {
throw new NullPointerException("輸入的年齡小於0");
}
}
/*
此方法手動拋出了運行時異常
運行時異常可以不用處理
*/
// 手動拋出編譯時異常
public void setAge2(int age) throws FileNotFoundException {
if (age < 0) {
throw new FileNotFoundException("輸入的年齡小於0");
}
}
/*
此方法手動拋出的了編譯時異常
編譯時異常需要被處理
這里采用了 throws 這個異常, 也就是說方法並沒有處理這個異常, 而是將異常拋給了調用者
這樣一來調用了這個方法的人就必須要處理這個異常才可以.
注意: 在這里並不用自己使用 try catch 處理這個異常
自己在方法里拋出異常, 方法再自己處理沒有任何作用.
所以方法中的異常需要拋給調用者去處理.
*/
public static void main(String[] args) {
ThrowEx throwEx = new ThrowEx();
throwEx.setAge(-5);
}
}
8. 自定義異常類
- 繼承現有的異常結構:RuntimeExceptiona(不用處理)、Exception(需要處理)
- 提供全局常量:serialVersionUID
- 提供重載的構造器
package com.broky.exception;
public class MyException extends RuntimeException{
static final long serialVersionUID = -1234719074324978L;
public MyException(){
}
public MyException(String message){
super(message);
}
public static void main(String[] args) {
throw new MyException("自定義運行時異常");
/*Exception in thread "main" com.broky.exception.MyException: 自定義運行時異常
at com.broky.exception.MyException.main(MyException.java:15)*/
}
}
9. 異常處理練習題
9.1. 判斷以下代碼的輸出內容
package com.broky.exception.practice;
public class ExceptionTest {
static void methodA() {
try {
System.out.println("運行A方法");
throw new RuntimeException("A方法手動拋出運行時異常");
} finally {
System.out.println("用A方法的finally");
}
}
static void methodB() {
try {
System.out.println("進入B方法");
return;
} finally {
System.out.println("用B方法的finally");
}
}
public static void main(String[] args) {
try {
methodA();
} catch (Exception e) {
System.out.println(e.getMessage());
}
methodB();
}
}
/*答案
運行A方法
用A方法的finally
A方法手動拋出運行時異常
進入B方法
用B方法的finally
*/
解析debug中的運行步驟:
- methodA();
- System.out.println("運行A方法");
- throw new RuntimeException("A方法手動拋出運行時異常"); //此處拋出了異常但是由於還在方法A的運行還沒有結束所以main中並沒有進行到catch環節,但是方法中的finally又必須運行,這樣一來方法中的finally必然在main中catch環節的前面
- System.out.println("用A方法的finally");
- atch (Exception e) {System.out.println(e.getMessage());}
- methodB();
9.2. 異常處理綜合練習
編寫應用程序 EcmDef java,接收命令行的兩個參數,要求不能輸入負數,計算兩數相除對數據類型不一致( Number FormatException)、
缺少命令行參數(ArraylndexOutofBounds Exception、除0( Arithmetic EXception)及輸入負數( EcDef自定義的異常)進行異常處理。
提示:
- 在主類( EcmDef)中定義異常方法(ecm)完成兩數相除功能。
- 在man(方法中使用異常處理語句進行異常處理
- 在程序中,自定義對應輸入負數的異常類( EcDef)。
- 運行時接受參數
java EcmDef 20 10
∥args[0]=“20“ args[1]=”10“ I - nterger類的 static方法 parseInt( String s)將s轉換成對應的int值
package com.broky.exception.practice;
public class EcDef extends Exception{
static final long serialVersionUID = -338751612344229948L;
public EcDef(){
}
public EcDef(String str){
super(str);
}
}
package com.broky.exception.practice;
public class EcmDef {
public static int ecm(int a, int b) throws EcDef, ArithmeticException {
if (a < 0 || b < 0)
throw new EcDef("輸入的數據小於0");
if (b == 0)
throw new ArithmeticException("除數不能等於0");
return a / b;
}
public static void main(String[] args) {
int a = 0;
int b = 0;
try {
a = Integer.parseInt(args[0]);
b = Integer.parseInt(args[1]);
System.out.println(ecm(a, b));
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("缺少命令行參數");
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (EcDef ecDef) {
ecDef.printStackTrace();
}
}
}