日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java 中的异常和处理详解

發布時間:2023/12/31 java 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 中的异常和处理详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

簡介

程序運行時,發生的不被期望的事件,它阻止了程序按照程序員的預期正常執行,這就是異常。異常發生時,是任程序自生自滅,立刻退出終止,還是輸出錯誤給給用戶?或者用 C 語言風格:用函數返回值作為執行狀態?
Java 提供了更加優秀解決辦法:異常處理機制。

異常處理機制能讓程序在異常發生時,按照代碼預先設定的異常處理邏輯,針對性地處理異常,讓程序盡最大可能恢復正常并繼續執行,且保持代碼的清晰。
Java 中的異常可以是函數中語句執行時引發的,也可以是程序員通過 throw 語句手動拋出的,只要在 Java 程序中產生了異常,就會用一個對應類型的異常對象來封裝異常,JRE 就會試圖尋找異常處理程序來處理異常。

Throwable 類是 Java 異常類型的頂層父類,一個對象只有是 Throwable 類的(直接或間接)實例,它才是一個異常對象,才能被異常處理機制識別。JDK 中內建了一些常用的異常類,我們也可以自定義異常。


Java 異常的分類和類結構圖

Java 標準庫內建了一些通用的異常,這些類以 Throwable 為頂層父類。
Throwable 又派生出 Error 類和 Exception 類。

錯誤:Error 類以及它的子類的實例,代表 JVM 本身的錯誤。錯誤不能被程序員通過代碼處理,Error 很少出現。因此,程序員應該關注 Exception 為父類的分支下的各種異常類。

異常:Exception 以及它的子類,代表程序運行時發送的各種不被期望發生的事件。可以被 Java 異常處理機制使用,是異常處理的核心。

總體上我們根據 Java 對異常的處理要求,將異常分為兩類。

非檢查異常(unchecked exception)
Error 和 RuntimeException 以及它們的子類。Java 在編譯時,不會提示和發現這樣的異常,不要求在程序中處理這些異常。所以如果愿意,我們可以編寫代碼處理(使用 try...catch...finally)這樣的異常,也可以不處理。對于這些異常,我們更應該的不是去處理這些異常,而是應該修正代碼。這樣的異常發生的原因多半是代碼邏輯寫的有問題。如除 0 錯誤 ArithmeticException,錯誤的強制類型轉換錯誤 ClassCastException,數組索引越界錯誤 ArrayIndexOutOfBoundsException,操作了空對象錯誤 NullPointerException 等等。

檢查異常(checked exception)
除了 Error 和 RuntimeException 的其它異常。Java 強制要求程序員為這樣的異常做預備處理工作(使用 try...catch...finally 或者 throws)。在方法中要么用 try...catch 語句捕獲它并處理,要么用 throws 子句聲明拋出它,否則編譯不會通過。這樣的異常一般是由程序的運行環境導致的。因為程序可能運行被運行在各種未知的環境下,而程序員無法干預用戶如何使用他編寫的程序,于是程序員就應該為這樣的異常時刻準備著。如 SQLExceptionIOExceptionClassNotFoundException 等。


初識異常

下面的代碼會演示2個異常類型:ArithmeticException 和 InputMimatchException。前者由于整數除 0 引發,后者是輸入的數據不能被轉化為 int 類型引發。

package com.example; import java. util .Scanner ; public class AllDemo {public static void main (String [] args ){System . out. println( "----歡迎使用命令行除法計算器----" ) ;CMDCalculate ();}public static void CMDCalculate (){Scanner scan = new Scanner ( System. in );int num1 = scan .nextInt () ;int num2 = scan .nextInt () ;int result = devide (num1 , num2 ) ;System . out. println( "result:" + result) ;scan .close () ;}public static int devide (int num1, int num2 ){return num1 / num2 ;} } /*****************************************----歡迎使用命令行除法計算器---- 0 Exception in thread "main" java.lang.ArithmeticException : / by zeroat com.example.AllDemo.devide( AllDemo.java:30 )at com.example.AllDemo.CMDCalculate( AllDemo.java:22 )at com.example.AllDemo.main( AllDemo.java:12 )----歡迎使用命令行除法計算器---- r Exception in thread "main" java.util.InputMismatchExceptionat java.util.Scanner.throwFor( Scanner.java:864 )at java.util.Scanner.next( Scanner.java:1485 )at java.util.Scanner.nextInt( Scanner.java:2117 )at java.util.Scanner.nextInt( Scanner.java:2076 )at com.example.AllDemo.CMDCalculate( AllDemo.java:20 )at com.example.AllDemo.main( AllDemo.java:12 ) *****************************************/

異常是在執行某個函數時引發的,而函數又是層級調用,形成調用棧的,因此,只要一個函數發生了異常,那么它的所有 caller 都會被異常影響。當這些被影響的函數以異常信息輸出時,就形成了異常追蹤棧。

異常最先發生的地方,叫做異常拋出點。

從上面的例子可以看出,當 devide 函數發生除 0 異常時,devide 函數拋出 ArithmeticExcepton 異常,因此調用它的 CMDCalculate 函數也無法正常完成,因此也發送異常,而 CMDCalculate 的 caller --main 因為 CMDCalculate 拋出異常,也發生了異常,這樣一直向調用棧的棧底回溯。這種行為叫做異常的冒泡,異常的冒泡是為了在當前發生異常的函數或者這個函數的 caller 中找到最近的異常處理程序。由于這個例子沒有使用任何異常處理機制,因此異常最終由 main 函數拋給 JRE,導致程序終止。

上面的代碼不使用異常處理機制,也可以順利編譯,因為2個異常都是非檢查異常。但是下面的例子就必須使用異常處理機制,因為異常是檢查異常。

代碼中選擇使用 throws 聲明異常,讓函數的調用者去處理可能發生的異常。但是為什么只 throws 了 IOException呢?因為 FileNotFoundException 是 IOException 的子類,在處理范圍內。

@Test public void testException() throws IOException {//FileInputStream的構造函數會拋出FileNotFoundExceptionFileInputStream fileIn = new FileInputStream("E:\\a.txt");int word;//read方法會拋出IOExceptionwhile((word = fileIn.read())!=-1) {System.out.print((char)word);}//close方法會拋出IOExceptionfileIn.clos }

異常處理的基本語法

在編寫代碼處理異常時,對于檢查異常,有2中不同的處理方式:使用 try...catch...finally 語句塊處理它。或者,在函數簽名中使用 throws 聲明交給函數調用者 caller 去處理。
try...catch...finally 語句塊

try{//try塊中放可能發生異常的代碼。//如果執行完try且不發生異常,則接著去執行finally塊和finally后面的代碼(如果有的話)。//如果發生異常,則嘗試去匹配catch塊。}catch(SQLException SQLexception){//每一個catch塊用于捕獲并處理一個特定的異常,或者這異常類型的子類。Java7中可以將多個異常聲明在一個catch中。//catch后面的括號定義了異常類型和異常參數。如果異常與之匹配且是最先匹配到的,則虛擬機將使用這個catch塊來處理異常。//在catch塊中可以使用這個塊的異常參數來獲取異常的相關信息。異常參數是這個catch塊中的局部變量,其它塊不能訪問。//如果當前try塊中發生的異常在后續的所有catch中都沒捕獲到,則先去執行finally,然后到這個函數的外部caller中去匹配異常處理器。//如果try中沒有發生異常,則所有的catch塊將被忽略。}catch(Exception exception){//... }finally{//finally塊通常是可選的。//無論異常是否發生,異常是否匹配被處理,finally都會執行。//一個try至少要有一個catch塊,否則, 至少要有1個finally塊。但是finally不是用來處理異常的,finally不會捕獲異常。//finally主要做一些清理工作,如流的關閉,數據庫連接的關閉等。 }

注:

  • try 塊中的局部變量和 catch 塊中的局部變量(包括異常變量),以及 finally 中的局部變量,它們之間不可共享使用。
  • 每一個 catch 塊用于處理一個異常。異常匹配是按照 catch 塊的的順序從上往下尋找的,只有第一個匹配的 catch 塊會得到執行。匹配時,不僅運行精確匹配,也支持父類匹配,因此,如果一個 try 塊下的多個 catch 異常類型有父子關系,應該將子類異常放在前面,父類異常放在后面,這樣保證每個 catch 塊都有存在的意義。
  • Java 中,異常處理的任務就是將執行控制流從異常發生的地方轉移到能夠處理這種異常的地方去。也就是說:當一個函數的某條語句發生異常時,這條語句的后面的語句都不會再執行,它失去了焦點。執行流跳轉到最近的匹配的異常處理 catch 代碼塊去執行,異常被處理完后,執行流會接著在“處理了這個異常的 catch 代碼塊” 后面接著執行。
  • 有的編程語言當異常被處理后,控制流會恢復到異常拋出點接著執行,這種策略叫做:resumption model of exception handing(恢復式異常處理模式)
    而 Java 則是讓執行流恢復到處理了異常的 catch 塊后接著執行,這種策略叫做:termination model of exception handing(終結式異常處理模式)

    public static void main(String[] args){try {foo();}catch(ArithmeticException ae) {System.out.println("處理異常");} } public static void foo(){int a = 5/0; //異常拋出點System.out.println("為什么還不給我漲工資!!!"); //不會執行 }

    throws 函數聲明
    throws 聲明:如果一個方法內部的代碼會拋出檢查異常(checked exception),而方法自己又沒有完全處理掉,則 Java 保證你必須在方法的簽名上使用 throws 關鍵字聲明這些可能拋出的異常,否則編譯不通過。

    throws 時另一種處理異常的方式,它不同于 try...catch...finally,throws 僅僅時將函數中可能出現的異常向調用者聲明,而自己則不具體處理。

    采取這種異常處理的原因可能是:方法本身不知道如何處理這樣的異常,或者說讓調用者處理更好,調用者需要為可能發生的異常負責。


    finally 塊

    finally 塊不管異常是否發生,只要對應的 try 執行了,則它一定也執行。只有一種方法可以讓 finally 塊不執行:System.exit(0)。因此 finally 塊通常用來做資源釋放操作:關閉文件,關閉數據庫連接等等。

    注:

  • finally 塊沒有處理異常的能力。處理異常的只能是 catch 塊。
  • 在同一 try...catch...finally 塊中,如果 try 中拋出異常,且有匹配的 catch 塊,則先執行 catch 塊,再執行 finally 塊。如果沒有 catch 塊匹配,則先執行 fianlly,然后再去外面的調用者中尋找合適的 catch 塊。
  • 在同一 try...catch...finally 塊中,try 發生異常,且匹配的 catch 塊中處理異常時拋出異常,那么后面的 finally 也會執行:首先執行 finally 塊,然后去外圍調用者中尋找合適的 catch 塊。

  • throw 異常拋出語句

    程序員也可以通過 throw 語句手動顯示的拋出一個異常。throw 語句的后面必須是一個異常對象。
    throw 語句必須寫在函數中,執行 throw 語句的地方就是一個異常拋出點,它和有 JRE 自動形成的異常拋出點沒有任何差別。

    public void save(User user) {if(user == null) throw new IllegalArgumentException("User對象為空");//......}

    異常的鏈化

    在一些大型的,模塊化的軟甲開發中,一旦一個地方發生異常,則如骨牌效應一般,將導致一連串的異常。假設 B 模塊完成自己的邏輯需要調用 A 模塊中的方法,如果 A 模塊發生異常,則 B 也將不能完成而發生異常,但是 B 在拋出異常時,會將 A 的異常信息掩蓋掉,這將使得異常的根源信息丟失。異常的鏈化可以將多個模塊的異常串聯起來,使得異常信息不會丟失。

    異常鏈化:以一個異常對象為參數構造新的異常對象。新的異常對象將包含先前異常的信息。這項技術主要是異常類的一個帶 Throwable 參數的函數來實現的。這個當作參數的異常,我們叫它根源異常(cause)。
    查看 Throwable 類源碼,可以發現里面有一個 Throwable 字段 cause,就是它保存了構造時傳遞的根源異常參數。這種設計和鏈表的節點類設計如出一轍,因此形成鏈也是自然的了。

    public class Throwable implements Serializable {private Throwable cause = this;public Throwable(String message, Throwable cause) {fillInStackTrace();detailMessage = message;this.cause = cause;}public Throwable(Throwable cause) {fillInStackTrace();detailMessage = (cause==null ? null : cause.toString());this.cause = cause;}//........ }

    自定義異常

    如果要自定義異常,則擴展 Exception 類即可,因此這樣的自定義異常都屬于檢查異常。如果要自定義非檢查異常,則擴展自 RuntimeException。
    自定義的異常應該總是包含如下的構造函數:

    • 一個無參構造函數
    • 一個帶有 String 參數的構造函數,并傳遞給父類的構造函數
    • 一個帶有 String 參數和 Throwable 參數,并都傳遞給父類構造函數
    • 一個帶有 Throwable 參數的構造函數,并傳遞給父類的構造函數

    下面是 IOException 類的完成源代碼,可以借鑒。

    public class IOException extends Exception {static final long serialVersionUID = 7818375828146090155L;public IOException(){super();}public IOException(String message){super(message);}public IOException(String message, Throwable cause){super(message, cause);}public IOException(Throwable cause){super(cause);} }

    異常的注意事項

  • 當子類重寫父類的帶有 throws 聲明的函數時,其 throws 聲明的異常必須在父類異常的可控范圍內--用于處理父類的 throws 方法的異常處理器,必須也適用于子類的這個帶 throws 方法。這是為了支持多態。
    例如,父類方法 throws 的是2個異常,子類就不能 throws 3個及以上的異常。父類 throws IOException,子類就必須 throws IOException 或者 IOException 的子類。
  • class Father {public void start() throws IOException{throw new IOException();} }class Son extends Father {public void start() throws Exception{throw new SQLException();} } /**********************假設上面的代碼是允許的(實質是錯誤的)***********************/ class Test {public static void main(String[] args){Father[] objs = new Father[2];objs[0] = new Father();objs[1] = new Son();for(Father obj:objs){//因為Son類拋出的實質是SQLException,而IOException無法處理它。//那么這里的try。。catch就不能處理Son中的異常。//多態就不能實現了。try {obj.start();}catch(IOException){//處理IOException}}} }
  • Java 程序可以是多線程的。每一個線程都是一個獨立的執行流,獨立的函數調用棧。如果程序只有一個線程,那么沒有被任何代碼處理的異常會導致程序終止。如果是多線程,那么沒有被任何代碼處理的異常僅僅會導致異常所在的線程結束。
    也就是說,Java 中的異常是線程獨立的,線程的問題應該由線程自己來解決,而不要委托到外部,也不會直接影響到其它線程的執行。

  • finally 塊和 return

    首先一個不容易理解的事實:在 try 塊中即便有 return,break,continue 等改變執行流的語句,finally 也會執行。

    public static void main(String[] args) {int re = bar();System.out.println(re); } private static int bar() {try{return 5;} finally{System.out.println("finally");} } /*輸出: finally */

    也就是說:try...catch.finally 中的 return 只要能執行,就都執行了,它們共同向同一個內存地址(假設地址是 0x80)寫入返回值,后執行的將覆蓋先執行的數據,而真正被調用者取的返回值就是最后一次寫入的。那么,按照這個思想,下面的這個例子也就不難理解了。
    finally 中的 return 會覆蓋 try 或者 catch 中的返回值。

    public static void main(String[] args){int result;result = foo();System.out.println(result); /2result = bar();System.out.println(result); /2}@SuppressWarnings("finally")public static int foo(){trz{int a = 5 / 0;} catch (Exception e){return 1;} finally{return 2;}}@SuppressWarnings("finally")public static int bar(){try {return 1;}finally {return 2;}}

    finally 中的 return 會抑制(消滅)前面 try 或者 catch 塊中的異常

    class TestException {public static void main(String[] args){int result;try{result = foo();System.out.println(result); //輸出100} catch (Exception e){System.out.println(e.getMessage()); //沒有捕獲到異常}try{result = bar();System.out.println(result); //輸出100} catch (Exception e){System.out.println(e.getMessage()); //沒有捕獲到異常}}//catch中的異常被抑制@SuppressWarnings("finally")public static int foo() throws Exception{try {int a = 5/0;return 1;}catch(ArithmeticException amExp) {throw new Exception("我將被忽略,因為下面的finally中使用了return");}finally {return 100;}}//try中的異常被抑制@SuppressWarnings("finally")public static int bar() throws Exception{try {int a = 5/0;return 1;}finally {return 100;}} }

    finally 中的異常會覆蓋(消滅)前面 try 或者 catch 中的異常

    class TestException {public static void main(String[] args){int result;try{result = foo();} catch (Exception e){System.out.println(e.getMessage()); //輸出:我是finaly中的Exception}try{result = bar();} catch (Exception e){System.out.println(e.getMessage()); //輸出:我是finaly中的Exception}}//catch中的異常被抑制@SuppressWarnings("finally")public static int foo() throws Exception{try {int a = 5/0;return 1;}catch(ArithmeticException amExp) {throw new Exception("我將被忽略,因為下面的finally中拋出了新的異常");}finally {throw new Exception("我是finaly中的Exception");}}//try中的異常被抑制@SuppressWarnings("finally")public static int bar() throws Exception{try {int a = 5/0;return 1;}finally {throw new Exception("我是finaly中的Exception");}} }

    上面的3個例子都異于常人的編碼思維,因此建議:

    • 不要在 finally 中使用 return
    • 不要在 finally 中拋出異常
    • 減輕 finally 的任務,不要在 finally 中做一些其它的事情,finally 塊僅僅用來釋放資源是最合適的
    • 盡量將所有的 return 寫在函數的最后面,而不是 try...catch...finally 中

    轉載于:https://my.oschina.net/u/4132929/blog/3050585

    總結

    以上是生活随笔為你收集整理的Java 中的异常和处理详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。