1. Java 中的異常
前言:Java 中的異常處理是處理程序運行錯誤時的強大機制之一,它可以保證應用程序的正常流程。
首先我們將了解java異常、異常的類型以及受查和非受查異常之間的區別。
1.1 什么是異常?
字面意義:異常是一種不正常的情況。
在 java 中,異常是擾亂程序正常流程的事件,它是在程序運行時拋出的對象。
1.2 什么是異常處理?
異常處理一種在運行時解決程序錯誤的機制,例如 ClassNotFound、IO、SQL、Remote 等。
1.2.1 異常處理的優勢
異常通常會干擾程序的正常流程,而異常處理的核心優勢是維護程序的正常流程。現在讓我們假設一下:
statement 1;
statement 2;
statement 3;
statement 4;
statement 5;//發生異常
statement 6;
statement 7;
statement 8;
statement 9;
statement 10;
假設你的程序中有10條語句,如果在第5條中出現了一個異常,那么語句6-10將不會繼續執行。如果你使用了異常處理,那么語句6-10的部分將正常執行,這就是我們為什么需要在程序中使用異常處理的原因。
你知道嗎?
- 受查和非受查異常之間的區別是什么?
- 代碼
int data=50/0;后面發生了什么?- 為什么需要使用多個
catch塊?finally塊是否有可能不執行?- 什么是異常傳遞?
throw和throws關鍵字之間的區別?- 對方法重寫使用異常處理的4條規則是什么?
現在讓我們帶着以上問題繼續下面的學習。
1.3 Java 異常類的層次結構

1.4 異常類型
主要有兩種類型的異常:受查和非受查異常,Error被視為非受查異常。Sun公司認為有三種異常類型:
- 受查異常(Checked Exception)
- 非受查異常(UnChecked Exception)
- 錯誤(Error)
1.5 受查和非受查異常之間的區別
1)受查異常
除了RuntimeException和Error外,繼承自Throwable類的類稱為受查異常,例如:IOException、SQLException 等。受查異常在編譯時進行檢查。
常見的有以下幾個方面:
- 試圖在文件尾部后面讀取數據
- 試圖打開一個不存在的文件
- 試圖根據給定的字符串查找Class對象,而這個字符串表示的類並不存在
2)非受查異常
繼承自RuntimeException類的異常被稱為非受查異常,例如:ArithmeticException、 NullPointerException、 ArrayIndexOutOfBoundsException 等。非受查異常不會在編譯時檢查,而是在運行時進行檢查。
常見的有以下幾個方面:
- 錯誤的類型轉換
- 數組訪問越界
- 訪問null指針
“如果出現了RuntimeException異常,那么一定是你自身的問題”,是一條相當有道理的規則。
3)錯誤(Error)
錯誤是一種無法恢復的異常類型,通常是在java運行時系統的內部錯誤和資源耗盡錯誤。應用程序不應該拋出這種類型的對象。如果出現了這樣的內部錯誤,除了通告給用戶,並盡力的使得程序安全的終止之外,再也無能為力了。這種情況很少出現。
1.6 可能出現異常的常見場景
在某些情況下,可能出現未檢查的異常,它們如下:
1)發生ArithmeticException的場景
如果我們將任何數字除以0,就會出現一個 ArithmeticException 異常。
int a = 50/0;//ArithmeticException
2)發生NullPointerException的場景
如果變量的值為null,那么調用此變量將會出現 NullPointerException 異常。
String s = null;
System.out.println(s.length());//NullPointerException
3)發生NumberFormatException的場景
任何值的格式錯誤,都有肯能發生 NumberFormatException 異常。假設一個字符串變量,其中包含了字符,若將此變量轉換為數字類型,將會發生 NumberFormatException 異常。
String s = "abc";
int i = Integer.parseInt(s);//NumberFormatException
4)發生ArrayIndexOutOfBoundsException的場景
如果你在一個不存在的的數組索引中插入任何值,則會導致 ArrayIndexOutOfBoundsException 異常。
int a[] = new int[5];
a[10] = 50; //ArrayIndexOutOfBoundsException
1.7 Java 異常處理關鍵字
下面是 Java 異常處理中的5個關鍵字:
try、catch、finally、throw、throws
1.8 創建自定義異常類
在程序中,可能會遇到任何標准異常類都沒有能夠充分地描述清楚的問題。在這種情況下,創建自己的異常類就是一件順理成章的事情了。我們需要做的只是定義一個派生於 Exception 的類,或者派生於 Exception 子類的類。例如,定義一個派生於 IOException 的類。
習慣上,定義的類應該包含兩個構造器,一個是默認構造器,一個是描述詳細信息的的構造器(超類 Throwable 的 toString 方法將會打印出這些詳細信息,這在調試中非常有用。)
示例如下:
class FileFormatException extends IOException {
public FileFormatException() {}
public FileFormatException(String gripe) {
super(gripe);
}
}
現在,就可以拋出自己定義的異常類型了。
String readData(BufferedReader in) throws FileFormatException {
...
while (...) {
// EOF encountered
if (ch == -1) {
if (n < len)
throw new FileFormatException();
}
...
}
return s;
}
2. Java try-catch
將可能發生異常的代碼放在try塊中,且必須在方法中才能使用。try 塊后必須使用catch塊或finally塊。
2.1 Java try 塊
1)try-catch 語法
try{
// 可能拋出異常的代碼
}catch(Exception_class_Name ref){}
2)try-finally 語法
try{
// 可能拋出異常的代碼
}finally{}
2.2 Java catch 塊
Java catch塊被用於處理異常,必須在try塊后使用。
你可以在一個try塊后使用多個catch塊
2.3 未使用異常處理的問題
如果我們不使用try-catch處理異常,看看會發生什么。
public class Testtrycatch1 {
public static void main(String args[]) {
int data=50/0;// 可能拋出異常
System.out.println("代碼的其余部分...");
}
}
輸出:
Exception in thread main java.lang.ArithmeticException:/ by zero
如上面的示例所示,代碼的其余部分並沒有執行。("代碼的其余部分..."未打印)
2.4 使用異常處理解決問題
讓我們通過try-catch塊來查看上述問題的解決方案。
public class Testtrycatch2 {
public static void main(String args[]) {
try {
int data = 50/0;
}
catch(ArithmeticException e) {
System.out.println(e);
}
System.out.println("代碼的其余部分...");
}
}
輸出:
Exception in thread main java.lang.ArithmeticException:/ by zero
代碼的其余部分...
現在,正如上面的示例所示,代碼的其余部分執行了.(也就是"代碼的其余部分..."被打印)
2.5 Java try-catch 內部工作原理

Java 虛擬機首先檢查異常是否被處理,如果異常未處理,則執行的一個默認的異常處理程序:
- 打印異常描述
- 打印堆棧跟蹤(異常發生方法的層次結構)
- 終止程序
如果程序員處理了異常,則應用程序按照正常流程執行。
3. 使用多個 catch 塊
如果需要在發生不同異常時執行不同的任務,則需要使用多個 catch 塊。
查看下面一個簡單的多 catch 塊示例。
public class TestMultipleCatchBlock{
public static void main(String args[]) {
try{
int a[] = new int[5];
a[5] = 30/0;
}
catch(ArithmeticException e) {
System.out.println("任務1已完成");
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("任務2已完成");
}
catch(Exception e) {
System.out.println("已完成通用任務");
}
System.out.println("代碼的其余部分...");
}
}
輸出:
任務1已完成
代碼的其余部分...
規則:一次只有一個異常發生,並且一次只執行一個catch塊。
規則: 所有異常必須從最具體到最通用的順序排序,即捕獲
ArithmeticException必須在捕獲Exception之前發生。
class TestMultipleCatchBlock1 {
public static void main(String args[]) {
try{
int a[]=new int[5];
a[5]=30/0;
}
catch(Exception e) {
System.out.println("已完成通用任務");
}
catch(ArithmeticException e) {
System.out.println("任務1已完成");
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("任務2已完成");
}
System.out.println("代碼的其余部分...");
}
}
輸出:
Compile-time error
4. Java 嵌套 try 塊
Java try塊中的try塊被稱為try嵌套塊。
4.1 為什么使用 try 嵌套塊?
有時可能會出現一種情況,一個塊的某個部分可能導致一個錯誤,而整個塊的本身可能會導致另一個錯誤。在這種情況下,必須使用嵌套異常處理程序。
語法:
....
try
{
statement 1;
statement 2;
try
{
statement 1;
statement 2;
}
catch(Exception e)
{
...
}
}
catch(Exception e) {...}
....
4.2 Java try 嵌套塊示例
class Excep6 {
public static void main(String args[]) {
try {
// try 嵌套塊1
try {
System.out.println("try 嵌套塊1");
int b = 39 / 0;
}
catch(ArithmeticException e) {
System.out.println(e);
}
// try 嵌套塊2
try {
int a[] = new int[5];
a[5] = 4;
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println(e);
}
System.out.println("try外部塊其他語句...");
}
catch(Exception e) {
System.out.println("handeled");
}
System.out.println("正常流...");
}
}
輸出:
try 嵌套塊1
java.lang.ArithmeticException: / by zero
java.lang.ArrayIndexOutOfBoundsException: 5
try外部塊其他語句...
正常流...
5. Java finally 塊
Java finally 塊是用來執行重要代碼的塊(如關閉連接、流等)。
無論是否處理異常,最終都會執行 finally 塊。
finally 塊緊跟 try 或 catch 塊后:

注意:無論你是否處理異常,在終止程序之前,JVM都將執行finally塊(如果存在的話)
5.1 為什么要使用 finally 塊
finally 塊可以用於放置"clear"代碼,例如關閉文件,關閉連接等。
5.2 使用 finally 塊案例
接下來讓我們來看看在不同情況下使用 finally 塊。
1)案例1
當前沒有發生異常:
class TestFinallyBlock {
public static void main(String[] args) {
try {
int data = 25 / 5;
System.out.println(data);
}
catch (NullPointerException e) {
System.out.println(e);
}
finally {
System.out.println("finally 塊總是執行");
}
System.out.println("代碼的其余部分...");
}
}
輸出:
5
finally 塊總是執行
代碼的其余部分...
2)案例2
發生異常但未處理:
class TestFinallyBlock1 {
public static void main(String[] args) {
try {
int data = 25 / 0;
System.out.println(data);
}
catch (NullPointerException e) {
System.out.println(e);
}
finally {
System.out.println("finally 塊總是執行");
}
System.out.println("代碼的其余部分...");
}
}
輸出:
finally 塊總是執行
Exception in thread main java.lang.ArithmeticException:/ by zero
3)案例3
發生異常並處理異常:
public class TestFinallyBlock2 {
public static void main(String args[]) {
try {
int data = 25 / 0;
System.out.println(data);
}
catch(ArithmeticException e) {
System.out.println(e);
}
finally {
System.out.println("finally 塊總是執行");
}
System.out.println("代碼的其余部分...");
}
}
輸出:
Exception in thread main java.lang.ArithmeticException:/ by zero
finally 塊總是執行
代碼的其余部分...
規則:對於 try 塊可以有0個或多個 catch 塊,但僅僅只能有一個 finally 塊。
規則:如果程序退出(通過調用 System.exit() 或通過導致進程中止的致命錯誤),finally塊將不會被執行。
6. Java 拋出異常
6.1 Java throw 關鍵字
Java throw 關鍵字用於顯示的拋出異常。
我們可以使用 throw 關鍵字在 Java 中拋出檢查(Checked)或未檢查(UnChecked)異常。throw 關鍵字主要用於拋出自定義異常。
Java throw 語法如下:
throw exception;
拋出IOException異常的例子:
throw new IOException("sorry device error");
6.2 Java throw 示例
在本例中,我們創建了一個將整數值作為參數的 validate 方法。如果年齡小於18歲,我們將拋出一個ArithmeticException異常,否則打印一條消息"歡迎投票"。
public class TestThrow1 {
static void validate(int age) {
if(age < 18)
throw new ArithmeticException("無效");
else
System.out.println("歡迎投票");
}
public static void main(String args[]) {
validate(13);
System.out.println("代碼的其余部分...");
}
}
輸出:
Exception in thread main java.lang.ArithmeticException:無效
7. Java 異常傳遞
異常首先從堆棧頂部拋出,如果未捕獲,則將調用堆棧下降到前一個方法,如果沒有捕獲,則將異常再次下降到先前的方法,以此類推,知道它們被捕獲或到達調用堆棧底部為止。以上稱為異常傳遞。
規則:默認情況下,非受查異常在調用鏈中(傳遞)轉發。
異常傳遞示例:
class TestExceptionPropagation1 {
void m(){
int data = 50 / 0;
}
void n() {
m();
}
void p() {
try{
n();
}
catch(Exception e) {
System.out.println("異常處理器");
}
}
public static void main(String args[]) {
TestExceptionPropagation1 obj = new TestExceptionPropagation1();
obj.p();
System.out.println("正常流...");
}
}
輸出:
異常處理器
正常流...

在上面的示例中。異常發生在 m() 方法中,如果未對其進行處理,則將其傳遞到未處理它的前 n() 方法,再次將其傳遞到處理異常的 p() 方法。
可以在 main()、p()、n()、p()、 m() 中的任何方法中處理異常。
規則:默認情況下,受查異常不會在調用鏈中(傳遞)轉發。
用於描述受查異常不會在程序中傳遞的示例:
class TestExceptionPropagation2{
void m(){
throw new java.io.IOException("設備異常"); // 受查異常
}
void n(){
m();
}
void p(){
try{
n();
}
catch(Exception e){
System.out.println("異常處理器");
}
}
public static void main(String args[]){
TestExceptionPropagation2 obj=new TestExceptionPropagation2();
obj.p();
System.out.println("正常流...");
}
}
輸出:
Compile Time Error
編譯時發生一個錯誤,證明受查異常並不會在程序中進行傳遞。
8. Java throws 關鍵字
Java throws 關鍵字被用於聲明一個異常。它給程序員提供了一個信息,說明可能會發生異常,所以程序員最好提供異常處理代碼,以保證程序正常的流程。
異常處理主要用於處理受查異常,如果出現任何非受查異常,如"NullPointerException",都是程序員自身的錯誤,請認真檢查你的代碼。
8.1 Java throws 語法
return_type method_name() throws exception_class_name {
// method code
}
8.2 應該聲明哪個異常?
僅僅聲明受查異常,因為:
- 非受查異常:程序員應該更正代碼以確保代碼正確無誤。
- Error:無法控制,如果出現了
VirtualMachineError或StackOverflowError等異常,將無法進行任何操作。
8.3 Java throws 優勢
使用 throws 聲明受查異常后,使得受查異常可以在調用堆棧中進行(傳遞)轉發。它向處理該異常的方法提供異常信息。
8.4 Java throws 示例
下面的示例描述了受查異常可以通過throws關鍵字進行傳遞:
import java.io.IOException;
class Testthrows1{
void m() throws IOException{
throw new IOException("設備異常"); // 受查異常
}
void n()throws IOException{
m();
}
void p(){
try{
n();
}
catch(Exception e){
System.out.println("異常處理器");
}
}
public static void main(String args[]){
Testthrows1 obj=new Testthrows1();
obj.p();
System.out.println("正常流...");
}
}
輸出:
異常處理器
正常流...
規則:如果你正在調用一個聲明了異常的方法,則必須捕獲或聲明異常。
現在有兩種情況:
- 情況1:你遇到了一個異常,使用 try-catch 處理了異常。
- 情況2:你聲明了異常,使用方法指定拋出。
1) 情況1:處理了異常
- 在這種情況下,如果你處理了異常,則不管程序是否出現了異常,程序都將繼續執行。
import java.io.*;
class M{
void method() throws IOException{
throw new IOException("設備異常");
}
}
public class Testthrows2{
public static void main(String args[]){
try{
M m = new M();
m.method();
}
catch(Exception e){
System.out.println("異常處理器");
}
System.out.println("正常流...");
}
}
輸出:
異常處理器
正常流...
2) 情況2:聲明了異常
- A)如果聲明了異常,但代碼未出現異常,程序將正常執行。
- B)如果聲明了異常且發生了異常,則在運行時拋出異常,因為程序會拋出不處理的異常。
A)聲明了異常但未發生異常:
import java.io.*;
class M{
void method()throws IOException{
System.out.println("執行設備操作");
}
}
class Testthrows3{
public static void main(String args[])throws IOException{
// 聲明了異常
M m=new M();
m.method();
System.out.println("正常流...");
}
}
輸出:
執行設備操作
正常流...
B)聲明了異常且發生了異常:
import java.io.*;
class M{
void method()throws IOException{
throw new IOException("設備錯誤");
}
}
class Testthrows4{
public static void main(String args[])throws IOException{
// 聲明了異常
M m=new M();
m.method();
System.out.println("正常流...");
}
}
輸出:
Runtime Exception
程序編譯時將直接出現了一個編譯錯誤。
8.5 throw 與 throws 區別
| No. | throw | throws |
|---|---|---|
| 1) | Java throw 關鍵字用於顯示的拋出異常 | Java throws 關鍵字用於聲明一個異常 |
| 2) | 受查異常不能只使用 throw 進行傳遞 | 受查異常可以通過 throws 進行傳遞 |
| 3) | Throw 后面跟着一個異常實例 | Throws 后面跟着一個異常類 |
| 4) | 在方法中使用 Throw | Throws 與方法簽名一起使用 |
| 5) | 你不能拋出多個異常 | 你可以聲明多個異常,例如public void method() throws IOException,SQLException |
1)Java throw 示例:
void m(){
throw new ArithmeticException("sorry");
}
2)Java throws 示例:
void m()throws ArithmeticException{
// method code
}
3)Java throw 和 throws 示例:
void m()throws ArithmeticException{
throw new ArithmeticException("sorry");
}
8.6 思考:可以重新拋出一個異常嗎?
答案當然是可以的,可以在 catch 塊中拋出相同的異常。這種方法通常用於只想記錄一個異常,但不做任何改變。
代碼示例:
try {
// access the database
}
catch (Exceptiom e) {
logger.log(level, message, e);
throw e;
}
在 Java SE7 之前,這種方法存在一個問題,假設這段代碼在以下方法中:
public void updateRecord() throws SQLException
Java 編譯器查看 catch 塊中的 throw 語句,然后查看 e 的類型,會指出這個方法可以拋出任何 Exception 而不僅僅是 SQLException。現在這個問題已經有所改進,編譯器會追蹤到 e 來自 try 塊。假設這個 try 塊僅有的受查異常是 SQLException 實例,另外,假設 e 在 catch 塊中未改變,將外圍方法聲明為 throws SQLException 是合法的。
9. Final 和 Finally 和 Finalize 對比
Final 和 Finally 和 Finalize 三者之間的差異如下:
| No. | final | finally | finalize |
|---|---|---|---|
| 1) | final 用於對類、方法和變量加以限制,final 類不能被繼承,final 方法不能被重寫,final 變量不能被更改 | finally 用於放置重要的代碼,無論異常是否被處理它都會執行 | finalize 用於在對象被垃圾回收之前執行清理操作 |
| 2) | final 是一個關鍵字 | finally 是一個塊 | finalize 是一個方法 |
1)Java final 示例:
class FinalExample{
public static void main(String[] args){
final int x = 100;
x = 200; // final 修飾的變量不能被更改
// 編譯時將出錯
}
}
2)Java finally 示例:
class FinallyExample{
public static void main(String[] args){
try{
int x = 300;
}
catch(Exception e){
System.out.println(e);
}
finally{
System.out.println("finally 塊始終被執行");
}
}
}
3)Java finalize 示例:
class FinalizeExample{
public void finalize(){
System.out.println("finalize called");
}
public static void main(String[] args){
FinalizeExample f1 = new FinalizeExample();
FinalizeExample f2 = new FinalizeExample();
f1 = null;
f2 = null;
System.gc();
}
}
10. 異常處理方法的重寫
關於重寫異常處理方法的規則如下:
- 超類方法沒有聲明異常:
如果超類方法沒有聲明異常,則子類重寫方法不能聲明受查異常,但可以聲明非受查異常。 - 超類方法聲明了異常:
如果超類方法聲明了異常,則子類重寫方法可以聲明與超類方法相同的異常,也可以不聲明異常。若父類方法聲明父類異常,子類重寫方法聲明子類異常也可以,反之不可以。
1)如果超類方法沒有聲明異常
超類方法未聲明異常,子類重寫方法聲明受查異常的示例:
import java.io.*;
class Parent{
void msg(){
System.out.println("parent");
}
}
class TestExceptionChild extends Parent{
void msg() throws IOException{
System.out.println("Child");
}
public static void main(String args[]){
Parent p = new TestExceptionChild();
p.msg();
}
}
輸出:
Compile Time Error
超類方法未聲明異常,子類重寫方法聲明非受查異常的示例:
import java.io.*;
class Parent{
void msg(){
System.out.println("parent");
}
}
class TestExceptionChild1 extends Parent{
void msg() throws ArithmeticException{
System.out.println("child");
}
public static void main(String args[]){
Parent p = new TestExceptionChild1();
p.msg();
}
}
輸出:
child
2)如果超類方法聲明了異常
A)超類方法聲明了異常,子類重寫方法聲明不相同父類異常的示例:
import java.io.*;
class Parent{
// 聲明了子類異常
void msg() throws ArithmeticException{
System.out.println("parent");
}
}
class TestExceptionChild2 extends Parent{
// 聲明了父類異常
void msg() throws Exception{
System.out.println("child");
}
public static void main(String args[]){
Parent p = new TestExceptionChild2();
try{
p.msg();
}
catch(Exception e){
}
}
}
輸出:
Compile Time Error
B)超類方法聲明了異常,子類重寫方法聲明相同異常的示例:
import java.io.*;
class Parent{
void msg()throws Exception{
System.out.println("parent");
}
}
class TestExceptionChild3 extends Parent{
void msg()throws Exception{
System.out.println("child");
}
public static void main(String args[]){
Parent p=new TestExceptionChild3();
try{
p.msg();
}
catch(Exception e){
}
}
}
輸出:
child
C)超類方法聲明了異常,子類重寫方法聲明不相同子類異常的示例:
import java.io.*;
class Parent{
// 聲明了父類異常
void msg()throws Exception{
System.out.println("parent");
}
}
class TestExceptionChild4 extends Parent{
// 聲明了子類異常
void msg()throws ArithmeticException{
System.out.println("child");
}
public static void main(String args[]){
Parent p=new TestExceptionChild4();
try{
p.msg();
}
catch(Exception e){
}
}
}
輸出:
child
D)超類方法聲明了異常,子類重寫方法未聲明異常的示例:
import java.io.*;
class Parent{
void msg()throws Exception{
System.out.println("parent");
}
}
class TestExceptionChild5 extends Parent{
void msg(){
System.out.println("child");
}
public static void main(String args[]){
Parent p=new TestExceptionChild5();
try{
p.msg();
}
catch(Exception e){
}
}
}
輸出:
child
參考文章:https://www.javatpoint.com/exception-handling-in-java
參考書籍:《Java核心技術 卷1》
