n 異常的概念
程序運行時,發生的不被期望的事件,它阻止了程序按照程序員的預期正常執行,這就是異常。異常發生時,是任程序自生自滅,立刻退出終止,還是輸出錯誤給用戶?
比如除法運算、讀寫文件操作,都可能發生異常。(當除數為0時;文件路徑不存在時)。該如何處理?
C語言的風格:用函數返回值作為執行狀態。比如返回一個為0的值表示文件不存在這個狀態。缺點是代碼比較散亂。
而Java語言提供了一種優秀的解決辦法:異常處理機制。java將處理異常的代碼放到一個統一的 try-catch-finally結構中去處理,且代碼易讀。請看下面的例子
Ø 示例一
數學運算之除0異常
import java.util.Scanner;
public class AllDemo {
public static void main(String[] args) {
System.out.println("----歡迎使用命令行除法計算器----");
Scanner scan = new Scanner(System.in);
int num1 = scan.nextInt();
int num2 = scan.nextInt();
int result = a(num1,num2);
System.out.println("result:" + result);
scan.close();
}
private static int a(int num1, int num2) {
return b(num1,num2);
}
private static int b(int num1, int num2) {
return devide(num1,num2);
}
public static int devide(int num1, int num2) {
return num1 / num2;
}
}
執行時輸入7和0,會看到控制臺結果
----歡迎使用命令行除法計算器----
7
0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at AllDemo.devide(AllDemo.java:24)
at AllDemo.b(AllDemo.java:20)
at AllDemo.a(AllDemo.java:16)
at AllDemo.main(AllDemo.java:9)
分析:當devide函數發生除0異常時,devide函數將拋出ArithmeticException異常,于是調用它的函數b也發生了異常,于是調用b的函數a也發生異常,于是調用a的函數main也發生了異常,這樣一直向調用棧的棧底回溯,這叫做異常的冒泡。這個例子沒有使用異常處理機制,異常最終由main函數拋給java虛擬機,導致程序終止。
Ø 示例二
讀寫文件異常
import java.io.FileInputStream;
import java.io.IOException;
class ReadFile {
public static void testException() throws IOException
{
//FileInputStream的構造函數會拋出FileNotFoundException
FileInputStream fileIn = new FileInputStream("E:\\a.txt");
int word;
while((word = fileIn.read())!=-1) //read方法會拋出IOException
{
System.out.print((char)word);
}
fileIn.close(); //close方法會拋出IOException
}
public static void main(String arg[]) throws IOException{
ReadFile.testException();
}
}
說明:
1、如果E盤下沒有文件a.txt,發生FileNotFoundException。
2、如果a.txt存在,但是被其它進程鎖住,有可能發生IOException
3、鑒于1、2,此處為了編譯正確,所以只得在testException()加上“throws IOException”,調用它的main()也得加上“throws IOException”。(注:FileNotFoundException是的IOException的子類)
n java異常處理的try-catch-finally結構
我們可以使用異常處理結構改進上面讀文件的例子
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
class ReadFile {
public static void testException() {
FileInputStream fileIn = null;
try {
// FileInputStream的構造函數會拋出FileNotFoundException
fileIn = new FileInputStream("E:\\a.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
int word;
try {
while ((word = fileIn.read()) != -1) // read方法可拋出IOException
{
System.out.print((char) word);
}
fileIn.close(); // close方法可拋出IOException
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("finally塊無論如何都要執行");
}
}
public static void main(String arg[]) {
ReadFile.testException();
}
}
說明
1、如果E盤下沒有文件a.txt,發生FileNotFoundException。進入catch結構,打印出一些信息。
2、程序仍然往下能繼續運行,在fileIn.read()時發生異常NullPointerException,這是因為第1步進了catch塊,這樣fileIn變量仍是最初的null值,一旦調函數便發生NullPointerException。
3、問:我們針對NullPointerException為什么不需要寫try-catch-finally結構?具體原因請閱讀后面的RuntimeException內容
4、這個例子中故意為了演示出NullPointerException,而把try-catch塊寫成了兩個,實際上可以合并。
n try-catch-finally結構說明
1、一個try可以對應多個catch塊。
2、如果發生異常,異常被拋給第一個catch 塊,如果異常的類型與 catch匹配,它在這里就會被捕獲。如果不匹配,它會被傳遞給第二個 catch 塊。如此,直到異常被捕獲或者通過所有的 catch 塊。
3、finally塊始終會被執行。
4、如果try或catch塊中存在return語句,那么catch、finally塊中的語句也會被執行完了后,才真正return。除非遇到下面的幾種情況(1)System.exit(n)可導致立即終止(2)finally塊中發生異常(3)程序所在線程死亡(4)關閉CPU。
練習
class Snippet {
public static String t() {
String s = "1";
try {
s = "2";
return s;
//throw new Exception("some");
} catch (Exception e) {
s = "3";
System.out.println("in catch block");
return s;
}finally {
s = "4";
System.out.println("in finally block");
return s;
}
}
public static void main(String[] args) {
String s= t();
System.out.println(s);
}
}
//無論如何,都要進到finally塊執行,如果finally{}里有return 那么返回的肯定是finally里的
Java異常類圖
從上面的示例代碼可以看出,java把異常也看成是對象。進而設計了下面所示的異常方面的類結構體系。最上層類叫做Throwable。可以把這些分成兩大種類
Ø 非檢查異常(unckecked exception):Error 和 RuntimeException 以及他們的子類。javac在編譯時,不會提示和發現這樣的異常,不要求程序員必須處理這些異常。在運行階段,倘若發生Error則虛擬機幾乎崩潰,倘若發生RuntimeException若程序員沒處理它則一直回溯向上拋給java虛擬機處理。當然,如果程序員愿意的話,也可以編寫代碼處理(使用try…catch…finally)這樣的異常(但是通常情況下不會這樣做。需要這樣做的情況是比如搞數學運算的這個專業領域要處理ArithmeticException)。對于這些異常,我們應該修正代碼,而不是去通過異常處理器處理。這種異常發生的原因多半是代碼寫的有問題。如除0錯誤ArithmeticException,錯誤的強制類型轉換錯誤ClassCastException,數組索引越界ArrayIndexOutOfBoundsException,使用了空對象NullPointerException等等。
Ø 檢查異常(checked exception):除了Error 和 RuntimeException的其它異常。javac強制要求程序員為這樣的異常做預備處理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch語句捕獲它并處理,要么用throws子句聲明拋出它,否則編譯不會通過。這樣的異常一般是由程序的運行環境導致的。因為程序可能被運行在各種未知的環境下,而程序員無法干預用戶如何使用他編寫的程序,于是程序員就應該為這樣的異常時刻準備著。如SQLException , IOException,ClassNotFoundException 等。
n 常見異常類型說明
異常類型說明
Exception 異常層次結構的父類
ArithmeticException算術錯誤情形,如以零作除數
ArrayIndexOutOfBoundsException數組下標越界
NullPointerException嘗試訪問 null 對象成員
ClassNotFoundException不能加載所需的類
IllegalArgumentException方法接收到非法參數
ClassCastException對象強制類型轉換出錯
NumberFormatException數字格式轉換異常,如把"abc"轉換成數字
n throws關鍵字
聲明本方法不處理異常,讓調用者處理。
在函數簽名中使用throws 聲明交給函數調用者caller去解決。
import java.util.Scanner;
public class HelloWorld {
public static void main(String[] args) {
try {
divide();//調用的此方法拋出了異常
}
catch (Exception e) {
System.out.println("錯誤:被除數和除數必須是整數,且除數不能為零。");
e.printStackTrace();
e.getMessage();//暫時打印不出任何內容
}
finally{
System.out.println("感謝使用本程序");
}
}
public static void divide() throws Exception{//聲明異常
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入被除數");
int num1 = scanner.nextInt();//有可能異常InputMismatchException
System.out.println("請輸入除數");
int num2 = scanner.nextInt();//有可能異常InputMismatchException
System.out.println(num1 / num2);//有可能異常除數等于0:ArithmeticException
}
}
n throw關鍵字
主動拋出異常
如果對于具體的一些處理邏輯,程序員也可以主動的拋出異常讓 外層處理。(此異常可能是程序員自己定義的)。
class Person {
private String name;
private String sex = "男";
public void setSex(String sex) throws Exception{
if("男".equals(sex) || "女".equals(sex)){
this.sex = sex;
}
else{
//主動拋出異常。也可做成自定義異常并拋出
throw new Exception("性別輸入錯誤,必須是男或女");
}
}
public void print() {
System.out.println(this.name + this.sex);
}
}
class Test {
public static void main(String[] args) {
Person p = new Person();
try {
p.setSex("male");
p.print();
} catch (Exception e) {
e.printStackTrace();
}
}
}
n 異常類的兩個打印異常信息的好辦法
一般,我們需要打印出異常的相關信息。在Exception類中,定義了下面兩個方法,
e.printStackTrace();//打印調用堆棧信息 并把e.getMessage()的信息也打出來了
e.getMessage();//打印異常的相關信息
與IOException類相似,我們自己定義的異常類往往也是Exception的子類,我們可以(1)覆蓋e.getMessage()方法; 也可以(2)構造的時候傳入具體的字符串信息,因為e.getMessage()就是獲取這個信息
示例
public class Test {
public static void main(String[] args) {
try {
Exception e = new Exception("哈哈,我是異常");
throw e;
} catch (Exception e) {
e.printStackTrace();
System.out.println("message="+ e.getMessage());
}
}
}
n 建議
(1)多重catch塊:Catch塊的排列順序必須是從子類到父類。最后一個一般是Exception。
(2)不要在fianlly中使用return。
不要在finally中拋出異常。
減輕finally的任務,不要在finally中做一些其它的事情,finally塊僅僅用來釋放資源是最合適的。
將盡量將所有的return寫在函數的最后面,而不是try … catch … finally中。
n 練習
import java.util.InputMismatchException;
import java.util.Scanner;
public class HelloWorld {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int num1=1 , num2=1;
try {
System.out.println("請輸入被除數");
num1 = scanner.nextInt();//有可能異常InputMismatchException
System.out.println("請輸入除數");
num2 = scanner.nextInt();//有可能異常InputMismatchException
System.out.println(String.format("%d / %d = %d", num1, num2, num1 / num2));//有可能異常除數等于0:ArithmeticException
}
catch(InputMismatchException e)
{
System.err.println("被除數和除數必須是整數");
return;
}
catch(ArithmeticException e)
{
System.err.println("除數不能為零");
//return;
System.exit(0);//這個是立即終止
}
catch (Exception e) {
System.out.println("錯誤:被除數和除數必須是整數,且除數不能為零。");
e.printStackTrace();
e.getMessage();//暫時打印不出任何內容
}
finally{
System.out.println("感謝使用本程序");
}
}
}