Java -入門 異常(自學筆記)(郝斌)

1、什麼是異常

  • 異常(Exception)是程序運行過程中發生的事件,該事件可以中斷程序指令的正常執行流程

異常的處理機制(重點)

  1. 當Java程序運行出現問題時,系統會自動檢測到該錯誤,並立即生成一個與該錯誤對應的異常對象
  2. 然後把該異常對象提交給Java虛擬機
  3. Java虛擬機會自動尋找相應的處理代碼來處理這個異常,如果沒有找到,則由Java虛擬機做一些簡單的處理後,程序被強行終止
  4. 程序員可以自己編寫代碼來捕捉可能出現的異常,並編寫代碼來處理相應的異常

throw

  • throw用來拋出異常

  • 格式:

    • throw new 異常名(參數)
  • 假設f方法拋出了A類異常,則f方法有兩種方式來處理A異常

    1. throws A
      • 誰調用f方法,誰處理A類異常,f方法本身不處理A類異常
    2. try{··· ···}catch( ){··· ···}
      • f方法本身自己來處理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除等系統錯誤、數組下標越界等,其產生比較頻繁,處理麻煩,對程序可讀性和運行效率影響太大。因此由系統檢測,用戶可不做處理,系統將它們交給缺省的異常處理程序(當然,必要時,用戶可對其處理)
        在這裏插入圖片描述
  1. Error 是系統錯誤,程序員無法處理這些異常
  2. Exception 是程序員可以捕獲並處理的異常
  3. RuntimeException的子類異常, 是你可以處理也可以不處理的異常
  4. 凡是繼承自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之間不能有其他代碼
  • 重寫方法拋出的異常的範圍不能大於被重寫方法排除的異常範圍
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章