【Java 異常】try-catch、finally、異常的種類、throws、thorw、自定義異常、斷言類

Java筆記目錄可以點這裏:Java 強化筆記

開發中的錯誤(語法錯誤、邏輯錯誤、異常)

在開發 Java 程序的過程中,會遇到各種各樣的錯誤:

  • 語法錯誤
    會導致編譯失敗,程序無法正常運行
  • 邏輯錯誤
    比如需要執行加法操作時,不小心寫成了減法操作
  • 運行時錯誤
    在程序運行過程中產生的意外,會導致程序終止運行
    在 Java 中也叫做異常

程序產生了異常,一般稱之爲:拋出了異常

  • 如果沒有主動去處理它,會導致程序終止運行

例:如果【代碼2】拋出了異常,並且沒有主動去處理它,程序就會退出,【代碼3】將沒有機會執行。

pubilc staic void main(String[] args) {
	【代碼1】
	【代碼2】
	【代碼3}

思考:下面代碼的打印結果是什麼?

public static void main(String[] args) {
	System.out.println(1);
	Integer i1 = new Integer("123");
	System.out.println(2);
	Integer i2 = new Integer("abc");
	System.out.println(3);
}

在這裏插入圖片描述
由於 "abc" 無法轉換成整數,new Integer("abc") 會拋出一個異常:

  • 異常類型:java.lang.NumberFormatException
  • 由於沒有主動去處理這個異常,所以導致程序終止運行

思考:下面代碼的打印結果是什麼?

public static void main(String[] args) {
	System.out.println(1);
	Integer i = new Integer("1234");
	Object obj = "1/2.34";
	Double d = (Double)obj;
	System.out.println(2);	
}

在這裏插入圖片描述
由於 "12.34" 無法強轉成 Double 類型,(Double)Object 會拋出一個異常

  • 異常類型:java.lang.ClassCastException
  • 由於沒有主動去處理這個異常,所以導致程序終止運行

思考:下面代碼的打印結果是什麼?

public static void main(String[] args) {
	Integer[] nums = { 11, null, 22 };
	for (int num : nums) {
		System.out.println(num);
	}
}

在這裏插入圖片描述
Integer 在自動拆箱爲 int 時,會調用 Integer 對象的 inValue() 方法;
由於 nums[1]null,使用 null 調用方法會拋出一個異常

  • 異常類型:java.lang.NullPointerException
  • 由於沒有主動去處理這個異常,所以導致程序終止運行

上面拋出異常的代碼建議這麼寫:

Integer[] nums= { 11, null, 22 };
for (Integer num : nums) {
	System.out.println(num);
}

打印的細節
Java 中直接打印 null 會報錯;
在這裏插入圖片描述
但是有時候確實可以把 null 打印出來:

public class Dog {
	@Override
	public String toString() {
		return "Dog - 666";
	}
}
Dog dog = new Dog();
System.out.println(dog); // Doge - 666

Dog dog2 = null;
System.out.println(dog2); // null

爲什麼 null 能被打印出來呢?可以看一下 println 的底層實現:發現調用了 String.valueOf

public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newLine();
    }
}

再看看 String.valueOf:可以發現打印出 null 其實是經過處理的。

public static String valueOf(Object obj) {
     return (obj == null) ? "null" : obj.toString();
 }

異常(Exception)

Java 中有各種各樣的異常

  • 所有的異常最終都繼承自 java.lang.Throwable
    在這裏插入圖片描述

如何防止程序因爲拋出異常導致終止運行?

  • 可以通過 try-catch 來捕捉處理異常

try-catch

在這裏插入圖片描述
思考:下面代碼的打印結果是什麼?

public static void main(String[] args) {
	System.out.println(1);
	try {
		System.out.println(2);
		Integer i = new Integer("abc");
		System.out.println(3);
	} catch (NumberFormatException e) {
		System.out.println(4);
	}
	System.out.println(5);
}
1
2
4
5

一個 catch 捕獲多種類型的異常

try {
	
} catch(異常A | 異常B | 異常 C) {
	// 當拋出【異常A】或【異常B】或【異常C】類型的異常時,會進入這個代碼塊
}
  • 從 Java 7 開始,單個 catch 可以捕獲多種類型的異常
  • 如果並列的幾個異常類型之間存在父子關係,保留父類型即可
  • 這裏的變量 e 是隱式 final 的

異常對象的常用方法(getMessage、printStackTrace)

try {
	Integer integer = new Integer("abc");
} catch (NumberFormatException e) {
	// 異常描述
	System.out.println(e.getMessage());
	// 異常名稱 + 異常
	System.out.println(e);
	// 打印堆棧信息
	e.printStackTrace();
}

在這裏插入圖片描述

finally

trycatch 正常執行完畢後,一定會執行 finally 中的代碼:

  • finally 可以和 try-catch 搭配使用,也可以只和 try 搭配使用
  • 經常會在 finally 中編寫一些關閉、釋放資源 的代碼(比如關閉文件)

在這裏插入圖片描述
finally 細節:
如果在執行 trycatch 時,JVM 退出或者當前線程被中斷、殺死

  • finally 可能不會執行

如果 trycatch 中使用了 returnbreakcontinue 等提前結束語句

  • finally 會在 returnbreakcontinue 之前執行

思考:下面代碼的打印結果是什麼?

for (int i = 1; i <= 3; i++) {
	try{
		System.out.println(i + "_try_1");
		if (i == 2) continue;
		System.out.println(i + "_try_2");
	} finally {
		System.out.println(i + "_finally");
	}
}
1_try_1
1_try_2
1_finally
2_try_1
2_finally
3_try_1
3_try_2
3_finally
for (int i = 1; i <= 3; i++) {
	try{
		System.out.println(i + "_try_1");
		if (i == 2) break;
		System.out.println(i + "_try_2");
	} finally {
		System.out.println(i + "_finally");
	}
}
1_try_1
1_try_2
1_finally
2_try_1
2_finally
public static void main(String[] args) {
	System.out.println(get());
}

static int get() {
	try {
		new Integer("abc");
		System.out.println(1);
		return 2;
	} catch (Exception e) {
		System.out.println(3);
		return 4;
	} finally {
		System.out.println(5);
	}
}
3
5
4

異常的種類(檢查型異常、非檢查型異常)

在這裏插入圖片描述
檢查型異常(Checked Exception)

  • 這類異常一般難以避免,編譯器進行檢查
    如果開發者沒有處理這類異常,編譯器將報錯
  • 哪些異常是檢查型異常?
    ErrorRuntimeException 以外的異常

非檢查型異常(Unchecked Exception)

  • 這類異常一般可以避免,編譯器不會進行檢查
    如果開發者沒有處理這類異常,編譯器將不會報錯
  • 哪些異常是非檢查型異常?
    ErrorRuntimeException

常見的檢查型異常
在這裏插入圖片描述
常見的非檢查型異常 – Error
在這裏插入圖片描述
常見的非檢查型異常 – RuntimeException
在這裏插入圖片描述

throws(作用、流程、細節)

throws 的作用:將異常拋給上層方法

void test() throws FileNotFoundException, ClassNotFoundException {
	PrintWriter out = new PrintWriter("F:/mj/520it.txt");
	Class cls = Class.forName("Dog");
}

如果 throws 後面的異常類型存在父子關係,保留父類型即可

void test() throws Exception {
	PrintWriter out = new PrintWriter("F:/mj/520it.txt");
	Class cls = Class.forName("Dog");
}
void test() throws Throwable {
	PrintWriter out = new PrintWriter("F:/mj/520it.txt");
	Class cls = Class.forName("Dog");
}

可以一部分異常使用 try-catch 處理,另一部分異常使用 throws 處理:

void test() throws FileNotFoundException {
	PrintWriter out = new PrintWriter("F:/mj/520it.txt");
	try {
		Class cls = Class.forName("Dog");
	} catch (ClassNotFoundException e) {
		e.printStackTrace();
	}
}

thorws 的流程

  • 如果異常最終拋給了 JVM,那麼整個 Java 程序將終止運行

下面這個例子中,異常從 method1 開始網上拋,到 method2,再到 method3,最後拋給 mainmain 又拋給了 JVM;

public static void main(String[] args) throws ClassNotFoundException {
	method1(); // 正常來說, 這裏必須處理異常了, 否則出異常後Java程序會終止運行
}
static void method1() throws ClassNotFoundException {
	method2(); // 這裏可以處理異常
}
static void method2() throws ClassNotFoundException {
	method3(); // 這裏可以處理異常
}
static void method3() throws ClassNotFoundException{
	Class clazz = Class.forName("Dog"); // 這裏可以處理異常
}

在這裏插入圖片描述
所以如果一直不處理異常,只往上拋最終會導致程序停止運行,一定要在某個地方處理異常。

throws 的細節
當父類的方法沒有 throws 異常:子類的重寫方法也不能 throws 異常
當父類的方法有 throws 異常,子類的重寫方法可以:

  • throws 異常
  • throws 跟父類一樣的異常
  • throws 父類異常的子類型
public class Person {
	public void test1() {}
	public void test2() throws IOException {}
	public void test3() throws IOException {}
	public void test4() throws IOException {}
	public void test5() throws IOException {}
}
public class Student extends Person {
	@Override
	public void test1() {}
	@Override
	public void test2() {}
	@Override
	public void test3() throws IOException {}
	@Override
	public void test4() throws FileNotFoundException {}
	// 只能拋出父類異常的子類, 或者不拋
	// public void test5() throws Exception {} // 拋出子類異常的父類異常, 會報錯
}

throw

使用 throw 可以拋出一個新建的異常

public class Person {
	public Person(String name) throws Exception { // 拋出異常
		if (name == null || name.length() == 0) {
			// 檢查型異常必須處理或者往上拋
			throw new Exception("name must not be empty.");
		}
	}
}
public class Person {
	public Person(String name)  {
		if (name == null || name.length() == 0) {
			// 非檢查型異常可以不處理
			throw new IllegalArgumentException("name must not be empty.");
		}
	}
}

自定義異常(Exception、RuntimeException)

開發中自定義的異常類型,基本都是以下 2 種做法:

  • 繼承自 Exception
    使用起來代碼會稍微複雜
    希望開發者重視這個異常、認真處理這個異常(該異常無法從代碼層面去避免,必須引起重視)
  • 繼承自 RuntimeException
    使用起來代碼會更加簡潔
    不嚴格要求開發者去處理這個異常(因爲規範的代碼可以避免該異常)

示例:
自定義兩種異常:EmptyNameExceptionWrongAgeException

public class EmptyNameException extends RuntimeException {
	public EmptyNameException() {
		super("name must not be empty");
	}
}
public class WrongAgeException extends RuntimeException {
	private int age;
	public WrongAgeException(int age) {
		super("wrong age:" + age + ", age must be > 0");
	}
}

Person 類中使用自定義的異常:

public class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
		if (name == null || name.length() == 0) {
			throw new EmptyNameException();
		}
		if (age <= 0) {
			throw new WrongAgeException(age);
		}
		this.name = name;
		this.age = age;
	}
}

使用 Person 類有機率拋出異常:

public static void main(String[] args) {
	//WrongAgeException: wrong age:-10, age must be > 0
	Person person = new Person("Jack", -10);
	// 這句代碼不會執行
	System.out.println(1);
}

使用異常的好處

  • 將錯誤處理代碼與普通代碼區分開
  • 能將錯誤信息傳播到調用堆棧中
  • 能對錯誤類型進行區分和分組

try-with-resources 語句

具體見:File、字符集、字節流、字符流、緩衝流、數據流、對象流、序列化、try-with-resources語句

從Java7開始推出的try-with-resources語句(可以沒有catchfinally

try(資源1; 資源2; ...) {

} catch (Exception e) {

} finally {

}

可以在try後面的小括號中聲明一個或多個資源(resource)

  • 實現了 java.lang.AutoCloseable 接口的實例,都可以稱之爲是資源

不管try中的語句是正常還是意外結束

  • 最終都會自動按順序調用每一個資源的 close 方法(close 方法的調用順序與資源的聲明順序相反
  • 調用完所有資源的 close 方法後,再執行 finally 中的語句

練習 - 編寫一個斷言類

/*
 * 斷言類
 */
public class Asserts {
	public static void test(boolean v) {
		if (v) return;
	 	// throw new RuntimeException();
		System.err.println(new RuntimeException().getStackTrace()[1]);
	}
}
public class Main {
	public static void main(String[] args) throws ClassNotFoundException {
		// int age = 10;
		// Asserts.test(age > 0);
		// String name = "";
		// Asserts.test(name != null && name.length() != 0);
		Asserts.test(add(10, 20) == 30);
		Asserts.test(sub(20, 10) == 20);	
	}
	static int add(int num1, int num2) {
		return num1 + num2;
	}
	static int sub(int num1, int num2) {
		return num1 - num2;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章