首頁 >Java >Java基礎 >你必須了解的java中的異常

你必須了解的java中的異常

尚
轉載
2019-11-29 17:21:171782瀏覽

你必須了解的java中的異常

一. 異常的定義(建議:java影片教學

在《java程式設計思想》中這樣定義 異常:阻止目前方法或作用域繼續執行的問題。雖然java中有異常處理機制,但是要明確一點,絕不應該用"正常"的態度來看待異常。絕對一點說異常就是某種意義上的錯誤,就是問題,它可能會導致程式失敗。之所以java要提出異常處理機制,就是要告訴開發人員,你的程式出現了不正常的狀況,請注意。

記得當初學習java的時候,異常總是搞不太清楚,不知道這個異常是什麼意思,為什麼會有這個機制?但隨著知識的累積逐漸也對異常有一點感覺了。舉一個例子來說明異常的用途。

public class Calculator {
    public int devide(int num1, int num2) {
        //判断除数是否为0
        if(num2 == 0) {
            throw new IllegalArgumentException("除数不能为零");
        }
         
        return num1/num2;
    }
}

看一下這個類別中關於除運算的方法,如果你是新手你可能會直接返回計算結果,根本不去考慮什麼參數是否正確,是否合法(當然可以原諒,誰都是這樣過來的)。但是我們應盡可能的考慮周全,把可能導致程式失敗的"苗頭"扼殺在搖籃中,所以進行參數的合法性檢查就很有必要了。

其中執行參數檢查拋出的那個參數非法異常,這就屬於這個方法的不正常情況。正常情況下我們會正確的使用計算器,但是不排除粗心大意把除數賦值為0。如果你之前沒有考慮到這種情況,並且剛好用戶數學基礎不好,那麼你就完了。但是如果你之前考慮到了這種情況,那麼很顯然錯誤已在你的掌控之中。

二. 異常掃盲行動

今天和別人聊天時看到一個笑話:世界上最真情的相依,是你在try我在catch。無論你發神馬脾氣,我都默默承受,靜靜處理。 大多數新手對java異常的感覺就是:try...catch...。沒錯,這是用的最多的,也是最實用的。我的感覺就是:java異常是從"try...catch..."走來。

首先來熟悉java的異常系統:

Throwable 類別是 Java 語言中所有錯誤或例外的超類別(這就是一切皆可丟棄的東西)。它有兩個子類別:Error和Exception。

Error:用於指示合理的應用程式不應該試圖捕獲的嚴重問題。這種情況是很大的問題,大到你不能處理了,所以聽之任之就行了,你不用管它。比如說VirtualMachineError:當 Java 虛擬機崩潰或用盡了它繼續操作所需的資源時,請拋出該錯誤。好吧,就算這個異常的存在了,那麼應該何時,該如何處理它呢? ?交給JVM吧,沒有比它更專業的了。

Exception:它指出了合理的應用程式想要捕獲的條件。 Exception又分為兩類:一種是CheckedException,一種是UncheckedException。

這兩種Exception的差異主要是CheckedException需要用try...catch...顯示的捕獲,而UncheckedException不需要捕獲。通常UncheckedException又叫做RuntimeException。

我們常見的RuntimeExcepiton有IllegalArgumentException、IllegalStateException、NullPointerException、IndexOutOfBoundsException等等。對於那些CheckedException就不勝枚舉了,我們在寫程式過程中try...catch...捕捉的異常都是CheckedException。 io套件中的IOException及其子類,這些都是CheckedException。 

三. 異常的使用

在異常的使用這一部分主要是示範程式碼,都是我們平常寫程式碼的過程中會遇到的(當然只是一小部分),拋磚引玉嗎!

範例1. 這個範例主要透過兩個方法比較來示範有了異常以後程式碼的執行流程。

public static void testException1() {
        int[] ints = new int[] { 1, 2, 3, 4 };
        System.out.println("异常出现前");
        try {
            System.out.println(ints[4]);
            System.out.println("我还有幸执行到吗");// 发生异常以后,后面的代码不能被执行
        } catch (IndexOutOfBoundsException e) {
            System.out.println("数组越界错误");
        }
        System.out.println("异常出现后");
    }
    /*output:
    异常出现前
    数组越界错误
    4
    异常出现后
    */
public static void testException2() {
        int[] ints = new int[] { 1, 2, 3, 4 };
        System.out.println("异常出现前");
        System.out.println(ints[4]);
        System.out.println("我还有幸执行到吗");// 发生异常以后,他后面的代码不能被执行
    }

首先指出例子中的不足之處,IndexOutofBoundsException是一個非受檢異常,所以不用try...catch...顯示捕捉,但是我的目的是對同一個異常用不同的處理方式,看看它會有什麼不同的而結果(這裡也就只能用它將就一下了)。異常出現時第一個方法只是跳出了try塊,但是它後面的程式碼會照樣執行的。

但是第二種就不一樣了直接跳出了方法,比較強硬。從第一個方法我們看到,try...catch...是一種"事務性"的保障,它的目的是保證程式在異常的情況下運行完畢,同時它也會告知程式設計師程式中出錯的詳細資訊(這種詳細資訊有時要依賴程式設計師設計)。

例2. 重新拋出異常

public class Rethrow {
    public static void readFile(String file) throws FileNotFoundException {
        try {
            BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.err.println("不知道如何处理该异常或者根本不想处理它,但是不做处理又不合适,这是重新抛出异常交给上一级处理");
            //重新抛出异常
            throw e;
        }
    }
     
    public static void printFile(String file) {
        try {
            readFile(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
     
    public static void main(String[] args) {
        printFile("D:/file");
    }
}

异常的本意是好的,让我们试图修复程序,但是现实中我们修复的几率很小,我们很多时候就是用它来记录出错的信息。如果你厌倦了不停的处理异常,重新抛出异常对你来说可能是一个很好的解脱。原封不动的把这个异常抛给上一级,抛给调用这个方法的人,让他来费脑筋吧。这样看来,java异常(当然指的是受检异常)又给我们平添很多麻烦,尽管它的出发点是好的。

例3. 异常链的使用及异常丢失

定义三个异常类:ExceptionA,ExceptionB,ExceptionC

public class ExceptionA extends Exception {
    public ExceptionA(String str) {
        super();
    }
}
 
public class ExceptionB extends ExceptionA {
 
    public ExceptionB(String str) {
        super(str);
    }
}
 
public class ExceptionC extends ExceptionA {
    public ExceptionC(String str) {
        super(str);
    }
}

异常丢失的情况:

public class NeverCaught {
    static void f() throws ExceptionB{
        throw new ExceptionB("exception b");
    }
 
    static void g() throws ExceptionC {
        try {
            f();
        } catch (ExceptionB e) {
            ExceptionC c = new ExceptionC("exception a");
            throw c;
        }
    }
 
    public static void main(String[] args) {
            try {
                g();
            } catch (ExceptionC e) {
                e.printStackTrace();
            }
    }
 
}
/*
exception.ExceptionC
at exception.NeverCaught.g(NeverCaught.java:12)
at exception.NeverCaught.main(NeverCaught.java:19)
*/

为什么只是打印出来了ExceptionC而没有打印出ExceptionB呢?这个还是自己分析一下吧!

上面的情况相当于少了一种异常,这在我们排错的过程中非常的不利。那我们遇到上面的情况应该怎么办呢?这就是异常链的用武之地:保存异常信息,在抛出另外一个异常的同时不丢失原来的异常。

public class NeverCaught {
    static void f() throws ExceptionB{
        throw new ExceptionB("exception b");
    }
 
    static void g() throws ExceptionC {
        try {
            f();
        } catch (ExceptionB e) {
            ExceptionC c = new ExceptionC("exception a");
            //异常连
            c.initCause(e);
            throw c;
        }
    }
 
    public static void main(String[] args) {
            try {
                g();
            } catch (ExceptionC e) {
                e.printStackTrace();
            }
    }
 
}
/*
exception.ExceptionC
at exception.NeverCaught.g(NeverCaught.java:12)
at exception.NeverCaught.main(NeverCaught.java:21)
Caused by: exception.ExceptionB
at exception.NeverCaught.f(NeverCaught.java:5)
at exception.NeverCaught.g(NeverCaught.java:10)
... 1 more
*/

这个异常链的特性是所有异常均具备的,因为这个initCause()方法是从Throwable继承的。

例4. 清理工作

清理工作对于我们来说是必不可少的,因为如果一些消耗资源的操作,比如IO,JDBC。如果我们用完以后没有及时正确的关闭,那后果会很严重,这意味着内存泄露。异常的出现要求我们必须设计一种机制不论什么情况下,资源都能及时正确的清理。这就是finally。

public void readFile(String file) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(
                    new FileInputStream(file)));
            // do some other work
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

例子非常的简单,是一个读取文件的例子。这样的例子在JDBC操作中也非常的常见。(所以,我觉得对于资源的及时正确清理是一个程序员的基本素质之一。)

Try...finally结构也是保证资源正确关闭的一个手段。如果你不清楚代码执行过程中会发生什么异常情况会导致资源不能得到清理,那么你就用try对这段"可疑"代码进行包装,然后在finally中进行资源的清理。举一个例子:

public void readFile() {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("file")));
            // do some other work
         
            //close reader
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }

我们注意一下这个方法和上一个方法的区别,下一个人可能习惯更好一点,及早的关闭reader。但是往往事与愿违,因为在reader.close()以前异常随时可能发生,这样的代码结构不能预防任何异常的出现。因为程序会在异常出现的地方跳出,后面的代码不能执行(这在上面应经用实例证明过)。这时我们就可以用try...finally来改造:

public void readFile() {
        BufferedReader reader = null;
        try {
            try {
                reader = new BufferedReader(new InputStreamReader(
                        new FileInputStream("file")));
                // do some other work
 
                // close reader
            } finally {
                reader.close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

及早的关闭资源是一种良好的行为,因为时间越长你忘记关闭的可能性越大。这样在配合上try...finally就保证万无一失了(不要嫌麻烦,java就是这么中规中矩)。

再说一种情况,假如我想在构造方法中打开一个文件或者创建一个JDBC连接,因为我们要在其他的方法中使用这个资源,所以不能在构造方法中及早的将这个资源关闭。那我们是不是就没辙了呢?答案是否定的。看一下下面的例子:

public class ResourceInConstructor {
    BufferedReader reader = null;
    public ResourceInConstructor() {
        try {
            reader = new BufferedReader(new InputStreamReader(new FileInputStream("")));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
     
    public void readFile() {
        try {
            while(reader.readLine()!=null) {
                //do some work
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
     
    public void dispose() {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这一部分讲的多了一点,但是异常确实是看起来容易用起来难的东西呀,java中还是有好多的东西需要深挖的。

四. 异常的误用

对于异常的误用着实很常见,上一部分中已经列举了几个,大家仔细的看一下。下面再说两个其他的。

例1. 用一个Exception来捕捉所有的异常,颇有"一夫当关万夫莫开"的气魄。不过这也是最傻的行为。

public void readFile(String file) {
        BufferedReader reader = null;
        Connection conn = null;
        try {
            reader = new BufferedReader(new InputStreamReader(
                    new FileInputStream(file)));
            // do some other work
             
            conn = DriverManager.getConnection("");
            //...
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

从异常角度来说这样严格的程序确实是万无一失,所有的异常都能捕获。但是站在编程人员的角度,万一这个程序出错了我们该如何分辨是到底是那引起的呢,IO还是JDBC...所以,这种写法很值得当做一个反例。大家不要以为这种做法很幼稚,傻子才会做。我在公司实习时确实看见了类似的情况:只不过是人家没有用Exception而是用了Throwable。

例2. 这里就不举例子了,上面的程序都是反例。异常是程序处理意外情况的机制,当程序发生意外时,我们需要尽可能多的得到意外的信息,包括发生的位置,描述,原因等等。

这些都是我们解决问题的线索。但是上面的例子都只是简单的printStackTrace()。如果我们自己写代码,就要尽可能多的对这个异常进行描述。比如说为什么会出现这个异常,什么情况下会发生这个异常。如果传入方法的参数不正确,告知什么样的参数是合法的参数,或者给出一个sample。

例3. 将try block写的简短,不要所有的东西都扔在这里,我们尽可能的分析出到底哪几行程序可能出现异常,只是对可能出现异常的代码进行try。尽量为每一个异常写一个try...catch,避免异常丢失。

更多java知識請關注java基礎教學欄位。

以上是你必須了解的java中的異常的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除