程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 計算機程序的思維邏輯 (24),思維邏輯

計算機程序的思維邏輯 (24),思維邏輯

編輯:JAVA綜合教程

計算機程序的思維邏輯 (24),思維邏輯


之前我們介紹的基本類型、類、接口、枚舉都是在表示和操作數據,操作的過程中可能有很多出錯的情況,出錯的原因可能是多方面的,有的是不可控的內部原因,比如內存不夠了、磁盤滿了,有的是不可控的外部原因,比如網絡連接有問題,更多的可能是程序的編程錯誤,比如引用變量未初始化就直接調用實例方法。

這些非正常情況在Java中統一被認為是異常,Java使用異常機制來統一處理,由於內容較多,我們分為兩節來介紹,本節介紹異常的初步概念,以及異常類本身,下節主要介紹異常的處理。

我們先來通過一些例子認識一下異常。

初始異常

NullPointerException (空指針異常)

我們來看段代碼:

public class ExceptionTest {
    public static void main(String[] args) {
        String s = null;
        s.indexOf("a");
        System.out.println("end");
    }
}

變量s沒有初始化就調用其實例方法indexOf,運行,屏幕輸出為:

Exception in thread "main" java.lang.NullPointerException
    at ExceptionTest.main(ExceptionTest.java:5)

輸出是告訴我們:在ExceptionTest類的main函數中,代碼第5行,出現了空指針異常(java.lang.NullPointerException)。

但,具體發生了什麼呢?當執行s.indexOf("a")的時候,Java系統發現s的值為null,沒有辦法繼續執行了,這時就啟用異常處理機制,首先創建一個異常對象,這裡是類NullPointerException的對象,然後查找看誰能處理這個異常,在示例代碼中,沒有代碼能處理這個異常,Java就啟用默認處理機制,那就是打印異常棧信息到屏幕,並退出程序。

在介紹函數調用原理的時候,我們介紹過棧,異常棧信息就包括了從異常發生點到最上層調用者的軌跡,還包括行號,可以說,這個棧信息是分析異常最為重要的信息。

Java的默認異常處理機制是退出程序,異常發生點後的代碼都不會執行,所以示例代碼中最後一行System.out.println("end")不會執行。

NumberFormatException (數字格式異常)

我們再來看一個例子,代碼如下:

public class ExceptionTest {
    public static void main(String[] args) {
        if(args.length<1){
            System.out.println("請輸入數字");
            return;
        }
        int num = Integer.parseInt(args[0]);
        System.out.println(num);
    }
}

args表示命令行參數,這段代碼要求參數為一個數字,它通過Integer.parseInt將參數轉換為一個整數,並輸出這個整數。參數是用戶輸入的,我們沒有辦法強制用戶輸入什麼,如果用戶輸的是數字,比如123,屏幕會輸出123,但如果用戶輸的不是數字,比如abc,屏幕會輸出:

Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.parseInt(Integer.java:527)
    at ExceptionTest.main(ExceptionTest.java:7)

出現了異常NumberFormatException。這個異常是怎麼產生的呢?根據異常棧信息,我們看相關代碼:

這是NumberFormatException類65行附近代碼:

64 static NumberFormatException forInputString(String s) {
65     return new NumberFormatException("For input string: \"" + s + "\"");
66 }

這是Integer類492行附近代碼:

490 digit = Character.digit(s.charAt(i++),radix);
491 if (digit < 0) {
492     throw NumberFormatException.forInputString(s);
493 }
494 if (result < multmin) {
495     throw NumberFormatException.forInputString(s);
496 }

將這兩處合為一行,主要代碼就是:

throw new NumberFormatException(...)

new NumberFormatException(...)是我們容易理解的,就是創建了一個類的對象,只是這個類是一個異常類。throw是什麼意思呢?就是拋出異常,它會觸發Java的異常處理機制。在之前的空指針異常中,我們沒有看到throw的代碼,可以認為throw是由Java虛擬機自己實現的。

throw關鍵字可以與return關鍵字進行對比,return代表正常退出,throw代表異常退出,return的返回位置是確定的,就是上一級調用者,而throw後執行哪行代碼則經常是不確定的,由異常處理機制動態確定。

異常處理機制會從當前函數開始查找看誰"捕獲"了這個異常,當前函數沒有就查看上一層,直到主函數,如果主函數也沒有,就使用默認機制,即輸出異常棧信息並退出,這正是我們在屏幕輸出中看到的。

對於屏幕輸出中的異常棧信息,程序員是可以理解的,但普通用戶無法理解,也不知道該怎麼辦,我們需要給用戶一個更為友好的信息,告訴用戶,他應該輸入的是數字,要做到這一點,我們需要自己"捕獲"異常。

"捕獲"是指使用try/catch關鍵字,我們看捕獲異常後的示例代碼:

public class ExceptionTest {
    public static void main(String[] args) {
        if(args.length<1){
            System.out.println("請輸入數字");
            return;
        }
        try{
            int num = Integer.parseInt(args[0]);
            System.out.println(num);    
        }catch(NumberFormatException e){
            System.err.println("參數"+args[0]
                    +"不是有效的數字,請輸入數字");
        }
    }
}

我們使用try/catch捕獲並處理了異常,try後面的大括號{}內包含可能拋出異常的代碼,括號後的catch語句包含能捕獲的異常和處理代碼,catch後面括號內是異常信息,包括異常類型和變量名,這裡是NumberFormatException e,通過它可以獲取更多異常信息,大括號{}內是處理代碼,這裡輸出了一個更為友好的提示信息。

捕獲異常後,程序就不會異常退出了,但try語句內異常點之後的其他代碼就不會執行了,執行完catch內的語句後,程序會繼續執行catch大括號外的代碼。

這樣,我們就對異常有了一個初步的了解,異常是相對於return的一種退出機制,可以由系統觸發,也可以由程序通過throw語句觸發,異常可以通過try/catch語句進行捕獲並處理,如果沒有捕獲,則會導致程序退出並輸出異常棧信息。異常有不同的類型,接下來,我們來認識一下。

異常類

Throwable

NullPointerException和NumberFormatException都是異常類,所有異常類都有一個共同的父類Throwable,它有4個public構造方法:

  1. public Throwable()
  2. public Throwable(String message)
  3. public Throwable(String message, Throwable cause)
  4. public Throwable(Throwable cause) 

有兩個主要參數,一個是message,表示異常消息,另一個是cause,表示觸發該異常的其他異常。異常可以形成一個異常鏈,上層的異常由底層異常觸發,cause表示底層異常。

Throwable還有一個public方法用於設置cause:

Throwable initCause(Throwable cause)

Throwable的某些子類沒有帶cause參數的構造方法,就可以通過這個方法來設置,這個方法最多只能被調用一次。

所有構造方法中都有一句重要的函數調用:

fillInStackTrace();

它會將異常棧信息保存下來,這是我們能看到異常棧的關鍵。

Throwable有一些常用方法用於獲取異常信息:

void printStackTrace()

打印異常棧信息到標准錯誤輸出流,它還有兩個重載的方法:

void printStackTrace(PrintStream s)
void printStackTrace(PrintWriter s)

打印棧信息到指定的流,關於PrintStream和PrintWriter我們後續文章介紹。

String getMessage()
Throwable getCause()

獲取設置的異常message和cause

StackTraceElement[] getStackTrace()

獲取異常棧每一層的信息,每個StackTraceElement包括文件名、類名、函數名、行號等信息。

異常類體系

以Throwable為根,Java API中定義了非常多的異常類,表示各種類型的異常,部分類示意如下:

Throwable是所有異常的基類,它有兩個子類Error和Exception。

Error表示系統錯誤或資源耗盡,由Java系統自己使用,應用程序不應拋出和處理,比如圖中列出的虛擬機錯誤(VirtualMacheError)及其子類內存溢出錯誤(OutOfMemoryError)和棧溢出錯誤(StackOverflowError)。

Exception表示應用程序錯誤,它有很多子類,應用程序也可以通過繼承Exception或其子類創建自定義異常,圖中列出了三個直接子類:IOException(輸入輸出I/O異常),SQLException(數據庫SQL異常),RuntimeException(運行時異常)。

RuntimeException(運行時異常)比較特殊,它的名字有點誤導,因為其他異常也是運行時產生的,它表示的實際含義是unchecked exception (未受檢異常),相對而言,Exception的其他子類和Exception自身則是checked exception (受檢異常),Error及其子類也是unchecked exception。

checked還是unchecked,區別在於Java如何處理這兩種異常,對於checked異常,Java會強制要求程序員進行處理,否則會有編譯錯誤,而對於unchecked異常則沒有這個要求。下節我們會進一步解釋。

RuntimeException也有很多子類,下表列出了其中常見的一些:

異常 說明 NullPointerException 空指針異常 IllegalStateException 非法狀態 ClassCastException 非法強制類型轉換 IllegalArgumentException 參數錯誤 NumberFormatException 數字格式錯誤 IndexOutOfBoundsException 索引越界 ArrayIndexOutOfBoundsException 數組索引越界 StringIndexOutOfBoundsException 字符串索引越界

這麼多不同的異常類其實並沒有比Throwable這個基類多多少屬性和方法,大部分類在繼承父類後只是定義了幾個構造方法,這些構造方法也只是調用了父類的構造方法,並沒有額外的操作。

那為什麼定義這麼多不同的類呢?主要是為了名字不同,異常類的名字本身就代表了異常的關鍵信息,無論是拋出還是捕獲異常時,使用合適的名字都有助於代碼的可讀性和可維護性。

自定義異常

除了Java API中定義的異常類,我們也可以自己定義異常類,一般通過繼承Exception或者它的某個子類,如果父類是RuntimeException或它的某個子類,則自定義異常也是unchecked exception,如果是Exception或Exception的其他子類,則自定義異常是checked exception。

我們通過繼承Exception來定義一個異常,代碼如下:

public class AppException extends Exception {
    public AppException() {
        super();
    }

    public AppException(String message,
            Throwable cause) {
        super(message, cause);
    }

    public AppException(String message) {
        super(message);
    }

    public AppException(Throwable cause) {
        super(cause);
    }
}

和很多其他異常類一樣,我們沒有定義額外的屬性和代碼,只是繼承了Exception,定義了構造方法並調用了父類的構造方法。

小結

本節,我們通過兩個例子對異常做了基本介紹,介紹了try/catch和throw關鍵字及其含義,同時介紹了Throwable以及以它為根的異常類體系。

下一節,讓我們進一步探討異常。

----------------

未完待續,查看最新文章,敬請關注微信公眾號“老馬說編程”(掃描下方二維碼),從入門到高級,深入淺出,老馬和你一起探索Java編程及計算機技術的本質。用心寫作,原創文章,保留所有版權。

-----------

更多好評原創文章

計算機程序的思維邏輯 (1) - 數據和變量

計算機程序的思維邏輯 (5) - 小數計算為什麼會出錯?

計算機程序的思維邏輯 (6) - 如何從亂碼中恢復 (上)?

計算機程序的思維邏輯 (8) - char的真正含義

計算機程序的思維邏輯 (12) - 函數調用的基本原理

計算機程序的思維邏輯 (17) - 繼承實現的基本原理

計算機程序的思維邏輯 (18) - 為什麼說繼承是把雙刃劍

計算機程序的思維邏輯 (19) - 接口的本質

計算機程序的思維邏輯 (20) - 為什麼要有抽象類?

計算機程序的思維邏輯 (21) - 內部類的本質

計算機程序的思維邏輯 (23) - 枚舉的本質

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved