1、什麼是異常
- 異常(Exception)是程序運行過程中發生的事件,該事件可以中斷程序指令的正常執行流程
異常的處理機制(重點)
- 當Java程序運行出現問題時,系統會自動檢測到該錯誤,並立即生成一個與該錯誤對應的異常對象
- 然後把該異常對象提交給Java虛擬機
- Java虛擬機會自動尋找相應的處理代碼來處理這個異常,如果沒有找到,則由Java虛擬機做一些簡單的處理後,程序被強行終止!
- 程序員可以自己編寫代碼來捕捉可能出現的異常,並編寫代碼來處理相應的異常
throw
-
throw用來拋出異常
-
格式:
- throw new 異常名(參數)
-
假設f方法拋出了A類異常,則f方法有兩種方式來處理A異常
- throws A
- 誰調用f方法,誰處理A類異常,f方法本身不處理A類異常
- try{··· ···}catch( ){··· ···}
- f方法本身自己來處理A類異常
- throws A
-
/**這個程序建議看完全篇再回來看*/ class DivisorIsZeroException extends Exception { public DivisorIsZeroException(String errormessage) { super(errormessage); } } class A { int divide(int a, int b) throws DivisorIsZeroException { // try // { // if(0 == b) // { // throw new DivisorIsZeroException("除數不能爲零!"); // } // } // catch(DivisorIsZeroException e) // { // e.printStackTrace();//打印錯誤信息的路徑 // } if(0 == b) { throw new DivisorIsZeroException("除數不能爲零!"); } int m = a/b; return m; } } public class TestA { public static void main(String[] args) { A aa = new A(); // aa.divide(6,2); } }
-
要拋出的異常必須得是Throwable的子類
-
例子:
void f() throws A
{
··· ···
··· ···
}
- throws A表示調用f方法時f方法可能會拋出A類異常,建議您調用f方法時最好對f方法可能拋出的A類異常進行捕捉
- throws A不表示調用f方法時一定會拋出A類異常
throws A,f方法也可以不拋出A類異常 - thorws A不表示調用f方法時,必須的對A異常進行捕捉
- 假設A是RuntimeException的子類異常
- 由於RuntimeException的子類異常可以處理也可以不處理,所以編譯器允許你調用f方法時,對f方法拋出的RuntimeException的子類異常不進行處理
- 強烈建議你
對throws出的所有異常進行處理
如果一個方法內部已經對A異常進行了處理,則就不要再throws A
2、爲什麼需要異常
我們再引入異常處理之前先舉一個例子:
C語言中的異常
#include <stdio.h>
int divide(int a,int b)
{
int m;
m = a/b;
return m;
}
int main()
{
// printf("%d\n",divide(6,3));
printf("%d\n",divide(6,0));
return 0;
}
執行以下命令:
————————————————————————
➜ JAVA gcc Test46.c
➜ JAVA ./a.out
————————————————————————
程序運行示例:
————————————————————————
[1] 1647 floating point exception ./a.out
————————————————————————
- 因爲除數爲0,所以導致C程序在運行的時候崩潰
Java 中的異常
class A
{
int divide(int a,int b)
{
//System.out.printf("1111\n");
int m = a/b;
// System.out.printf("2222\n");
return m;
}
}
public class Test46
{
public static void main(String[] args)
{
A aa = new A();
aa.divide(6,0); //編譯時,無錯,運行時,有錯
System.out.printf("今天我很高興,因爲我和世界的關係很nice\n");
}
}
執行以下命令:
————————————————————————
➜ JAVA javac Test46.java
➜ JAVA java Test46
————————————————————————
程序運行示例:
————————————————————————
Exception in thread "main" java.lang.ArithmeticException: / by zero
at A.divide(Test46.java:5) //程序第五行異常出錯
at Test46.main(Test46.java:16) //程序第十六行異常出錯
————————————————————————
- 因爲這個程序也是除數是0,所以程序運行出現異常
**我們在程序的第六行的前後分別插入: **
System.out.printf("1111\n"); //插入第六行的前面
System.out.printf("2222\n"); //插入第六行的後面
執行以下命令:
————————————————————————
➜ JAVA javac Test46.java
➜ JAVA java Test46
————————————————————————
程序運行示例:
————————————————————————
1111
Exception in thread "main" java.lang.ArithmeticException: / by zero
at A.divide(Test46.java:6)
at Test46.main(Test46.java:17)
————————————————————————
- 再次證明程序在第六行異常出錯終止程序
3、異常的處理機制
class A
{
int divide(int a,int b)
{
// System.out.printf("1111\n");
int m = a/b;
// System.out.printf("2222\n");
return m;
}
}
public class Test46
{
public static void main(String[] args)
{
A aa = new A();
// aa.divide(6,0); //編譯時時,無錯,運行時,有錯
try
{
aa.divide(6,0);
}
catch(ArithmeticException e) //算術異常 e用來接收23行拋出的異常對象
{
e.printStackTrace();//可以簡單理解爲輸出該異常的具體信息
System.out.printf("除零錯誤,你的程序出錯了!\n");
}
System.out.printf("今天我很高興,因爲我和世界的關係很nice\n");
}
}
執行以下命令:
————————————————————————
➜ JAVA javac Test46.java
➜ JAVA java Test46
————————————————————————
程序運行示例:
————————————————————————
java.lang.ArithmeticException: / by zero
at A.divide(Test46.java:6)
at Test46.main(Test46.java:20)
除零錯誤,你的程序出錯了!
今天我很高興,因爲我和世界的關係很nice
————————————————————————
-
把可能會出錯的語句放入語句
-
try { ··· ··· } catch { }
中的try{ } 語句塊。如果該語句符合規範的,則不會對該語句進行處理,即程序會自動跳過==catch{}==語句執行其下一行語句,示例:
class A { int divide(int a,int b) { int m = a/b; return m; } } public class Test46 { public static void main(String[] args) { A aa = new A(); // aa.divide(6,0); //編譯時時,無錯,運行時,有錯 try { aa.divide(6,2); } catch(ArithmeticException e) //算術異常 e用來接收23行拋出的異常對象 { e.printStackTrace();//可以簡單理解爲輸出該異常的具體信息 System.out.printf("除零錯誤,你的程序出錯了!\n"); } System.out.printf("這是catch語句的下一條語句,因爲返回值可以正常輸出\n"); System.out.printf("今天我很高興,因爲我和世界的關係很nice\n"); } }
執行以下命令: ———————————————————————— ➜ JAVA javac Test46.java ➜ JAVA java Test46 ———————————————————————— 程序運行示例: ———————————————————————— 這是catch語句的下一條語句,因爲返回值可以正常輸出 今天我很高興,因爲我和世界的關係很nice ————————————————————————
補充:
public class Test48
{
public static void main(String[] args)
{
int m = 99;
try
{
m = 2;
}
catch(Exception e)
{
}
System.out.printf("m = %d\n",m);
}
}
執行以下命令:
————————————————————————
➜ JAVA javac Test48.java
➜ JAVA java Test48
————————————————————————
程序運行示例:
————————————————————————
m = 2
————————————————————————
若把第五行的m未初始化,即修改第五行爲
int m;
執行以下命令:
————————————————————————
➜ JAVA javac Test48.java
————————————————————————
程序運行示例:
————————————————————————
Test48.java:14: 錯誤: 可能尚未初始化變量m
System.out.printf("m = %d\n",m);
^
1 個錯誤
————————————————————————
爲什麼?
倘若我們把14行的輸出語句放入==try{}==語句內,即
public class Test48
{
public static void main(String[] args)
{
int m;
try
{
m = 2;
System.out.printf("m = %d\n",m);
}
catch(Exception e)
{
}
// System.out.printf("m = %d\n",m);
}
}
執行以下命令:
————————————————————————
➜ JAVA javac Test48.java
————————————————————————
程序運行示例:
————————————————————————
m = 2
————————————————————————
同樣是局部變量m,爲什麼這個m可以在try{}塊內部輸出,但是卻不能在try{}塊之外輸出呢?
- 因爲try{}語句存放程序中可能存在風險的語句,換句話說,對於其花括號裏的語句,運行程序時並不一定保證能夠順利執行,否則程序員也就不會把好好的語句朝==try{}語句塊裏塞了,綜上,倘若try{}==語句未能順利執行,則對於讓JAVA輸出未初始化的的局部變量是不允許的。
異常的分類:
-
Throwable(拋出)
- Error(嚴重的錯誤)
- 由Java虛擬機生成並拋出,包括動態鏈接失敗、虛擬機錯誤等,Java程序無法對此錯誤進行處理。
- Exception(此集合中的異常必須處理)
- 一般程序中可預知的問題,其產生的異常可能會帶來意想不到的結果,因此==Java編譯器要求Java程序必須捕獲或聲明所有的非運行時異常==
- RuntimeException(此集合的異常可處理可不處理)
- Java虛擬機在運行時生成的異常,如被0除等系統錯誤、數組下標越界等,其產生比較頻繁,處理麻煩,對程序可讀性和運行效率影響太大。因此由系統檢測,用戶可不做處理,系統將它們交給缺省的異常處理程序(當然,必要時,用戶可對其處理)
- Java虛擬機在運行時生成的異常,如被0除等系統錯誤、數組下標越界等,其產生比較頻繁,處理麻煩,對程序可讀性和運行效率影響太大。因此由系統檢測,用戶可不做處理,系統將它們交給缺省的異常處理程序(當然,必要時,用戶可對其處理)
- Error(嚴重的錯誤)
- Error 是系統錯誤,程序員無法處理這些異常
- Exception 是程序員可以捕獲並處理的異常
- RuntimeException的子類異常, 是你可以處理也可以不處理的異常
- 凡是繼承自Exception但又不是RuntimeException子類的異常我們都必須的捕獲並進行處理
4、異常處理步驟:
-
thorw try catch finally throws
try { 可能出現異常的代碼塊 } catch(ExceptionName1) { 當產生ExceptionName1 異常時的處理措施 } catch(ExceptionName2) { 當產生ExceptionName2 異常時的處理措施 } ··· ··· finally { 無論是否捕捉到異常都必須處理的代碼 }
- 首先在出現異常的語句周圍查找是否有**try{} 和catch{} **語句
- 其次在主調函數中的調用語句附近查找是否有**try{} 和catch{} **語句
- 如果都沒有,則有Java虛擬機簡單處理**(終止程序)**
5、自定義異常
- ==public Exception(String message)==含義
- 異常與重寫關係
6、異常的優缺點
下面說一些目前爲止關於異常的困惑:
-
目前爲止if····else···語句似乎完全可以替代異常處理機制
-
顯然,在某些方法上似乎是的,但是這不是完全的,下面就具體列舉一些例子:
-
import java.util.*; public class Test50 { public static void main(String[] args) { Scanner sc = null; try { sc = new Scanner(System.in); int i = sc.nextInt();//輸入的字符類型a與int類型不匹配 System.out.printf("%d\n",i); } catch(InputMismatchException e) { System.out.println("讀取錯誤,程序將終止"); } } } 程序運行示例: —————————————————————— ➜ JAVA javac Test50.java ➜ JAVA java Test50 a 讀取錯誤,程序將終止 ➜ JAVA java Test50 34 34 ➜ JAVA ——————————————————————
-
本程序出現的問題是無法通過邏輯判斷來解決的問題Java提供的異常處理機制可以很好的解決這個問題
異常的優點
-
沒有錯誤處理的程序:
- openTheFile
- determine its size
- Allocate that much memory
- read-file
- closeTheFile
-
以常規方法處理錯誤:
openFile: if(the FileOpen) { determine the length of the file; if(gotTheFileLength) { allocate that much memory; if(gotEnoughtMemory) { read the file into memory; if(readFailed) errorCode = -1; else errorCode = -2; } else errorCode = -3; } else errorCode = -4; } else errorCode = -5;
以常規方法處理錯誤存在的問題
- 觀察前面的程序,大家會發現大部分精力花在出錯處理上了
- 只把能夠想到的錯誤考慮到,對以外的情況無法處理
- 程序可讀性差,大量的錯誤處理代碼混雜在程序中
- 出錯返回信息量少,無法更確切的瞭解錯誤狀況或原因
-
用異常的形式處理錯誤:
{
try
{
openFile;
determine the length of the file;
allocate that much memory;
read-File;
closeTheFile;
}
catch(fileopenFailed) {dosomething}
catch(sizeDetemineFailed) {dosomething}
catch(memoryAllocatedFailed) {dosomething}
catch(readFailed) {dosomething}
catch(fileCloseFailed) {dosomething}
finally {dosomething}
}
優點
- 強制程序員考慮程序的安全性與健壯性
- 增強了程序員對程序的可控性
- 有利於代碼的調試
- 把錯誤處理代碼從常規代碼中分離出來
注意:
- 異常並不一定能夠使程序的邏輯更清晰
- 因爲有時我們必須得編寫代碼捕捉異常,所以可能會導致程序的邏輯非常混亂
- 異常並不能解決所有的問題
7、常見異常舉例:
常見異常之空指針異常
class Person
{
public int age;
}
public class TestNullPointerException
{
public static void main(String[] args)
{
Person p = null; //定義指針賦爲空
System.out.println(p.age);
}
}
程序運行示例:
——————————————————————
➜ JAVA javac TestNullPointerException.java
➜ JAVA java TestNullPointerException
Exception in thread "main" java.lang.NullPointerException
at TestNullPointerException.main(TestNullPointerException.java:10)
——————————————————————
解決辦法:
class Person
{
public int age;
}
public class TestNullPointerException
{
public static void main(String[] args)
{
// Person p = null;
Person p = new Person();
System.out.println(p.age); //age在未初始化的情況下,系統自動爲其賦值爲0
}
}
程序運行示例:
——————————————————————
➜ JAVA javac TestNullPointerException.java
➜ JAVA java TestNullPointerException
0
——————————————————————
常見異常之下標越界異常
public class TestIndexOutOf
{
public static void main(String[] args)
{
String friends[] = {"Lisa","Bily","Kessy"};
for(int i = 0; i < 5; i++)
{
System.out.println(friends[i]);
}
System.out.println("\nthis is the end");
}
}
程序運行示例:
——————————————————————
➜ JAVA javac TestIndexOutOf.java
➜ JAVA java TestIndexOutOf
Lisa
Bily
Kessy
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at TestIndexOutOf.main(TestIndexOutOf.java:8)
——————————————————————
常見異常之除數爲0異常
class A
{
int divide(int a, int b)
{
return a/b;
}
}
public class TestArithExcep
{
public static void main(String[] args)
{
A aa = new A();
int i = aa.divide(3,0);
System.out.println(i);
}
}
程序運行示例:
——————————————————————
➜ JAVA javac TestArithExcep.java
➜ JAVA java TestArithExcep
Exception in thread "main" java.lang.ArithmeticException: / by zero
at A.divide(TestArithExcep.java:5)
at TestArithExcep.main(TestArithExcep.java:13)
——————————————————————
8、異常處理的兩種方式:
import java.io.*;
class A
{
public void f() throws IOException //throws語句表示交給主調函數處理 //第二種方法
{
/* 第一種方法
try
{
throw new IOException(); //throw不表示拋異常
}
catch(IOException e)
{
}
*/
throw new IOException(); //throw不表示拋異常
}
}
public class TestExcep
{
public static void main(String[] args) throws IOException /*throws語句表示交給Java虛擬機處理*/ //第二種方法
{
A aa = new A();
aa.f();
// try //第一種方法
// {
// aa.f();
// }
// catch(IOException e)
// {
// }
}
}
9、Finally的作用
- 無論try所指定的程序塊中是否拋出異常,也無論catch語句的異常類型是否與所拋棄的異常的類型一致,finally中的代碼一定會得到執行
- finally語句爲異常處理提供一個統一的出口,使得在控制流程轉到程序的其他部分以前,能夠對程序的狀態作統一的管理
- 通常在finally語句中可以進行資源的清除工作,如關閉打開的文件、刪除臨時文件等
10、注意問題
- 所有的catch只能有一個被執行
- 有可能所有的catch都沒有執行
- 先catch子類異常再catch父類異常
- 如果先catch父類異常再catch子類異常,則編譯出錯
- catch與catch之間不能有其他代碼
- 重寫方法拋出的異常的範圍不能大於被重寫方法排除的異常範圍