Java基礎(chǔ)之異常機(jī)制學(xué)習(xí)&分析

Java異常機(jī)制學(xué)習(xí)&分析
處理錯(cuò)誤
Java異常層次簡(jiǎn)要類圖
何時(shí)聲明受查異常

    調(diào)用一個(gè)拋出受查異常的方法,例如, FileInputStream構(gòu)造器
    程序運(yùn)行過程中發(fā)現(xiàn)錯(cuò)誤,并且利用throw語(yǔ)句拋出一個(gè)受查異常
    程序出現(xiàn)錯(cuò)誤,例如,a[-1]=0會(huì)拋出一個(gè)ArrayIndexOutOfBoundsException這樣的非受查異常。
    Java 虛擬機(jī)和運(yùn)行時(shí)庫(kù)出現(xiàn)的內(nèi)部錯(cuò)誤。

如果出現(xiàn)前兩種情況之一,則必須告訴調(diào)用這個(gè)放啊的程序員有可能拋出異常。為什么?因?yàn)槿魏我粋€(gè)拋出異常的方法都可能是一個(gè)死亡陷阱。
如果沒有處理器捕獲這個(gè)異常,當(dāng)前執(zhí)行的線程就會(huì)結(jié)束。
如下所示:

  public Image loadImage(String s) throws IOException {
        return null;
    }

    public Image loadImage2(String s) throws FileNotFoundException,EOFException {
        return null;
    }



如果在子類中覆蓋了超類的一個(gè)方法,子類方法中聲明的受查異常不能比超類方法中聲明的異常更通用(也就是說,子類方法中可以
拋出更特定的異常,或者根本不拋出任何異常)。特別需要說明的是,如果超類方法沒有拋出任何受查異常,子類也不能拋出任何受查異常。
自定義異常類

public class FileFormatException extends IOException {
    public FileFormatException() {

    }

    public FileFormatException(String msg) {
        super(msg);
    }

}


捕獲異常

以下代碼:

 FileInputStream in = new FileInputStream("test.txt");
        try {
            //1
//        code that might throw exception
            //2
        } catch (IOException e) {
           //3
            //show error message
            //4
        }
        finally {
            //5
            //in.close();
        }
        //6


有下列3中情況會(huì)執(zhí)行finally子句
1. 代碼沒有拋出異常,在這種情況下,程序首先執(zhí)行try語(yǔ)句塊中的全部代碼,然后執(zhí)行finally子句
中的代碼,隨后,繼續(xù)執(zhí)行try語(yǔ)句塊之后的第一條語(yǔ)句。也就是說,執(zhí)行順序:1,2,5,6
2. 拋出一個(gè)在catch子句中捕獲的異常。在上面的實(shí)例中就是IOException異常。在這種情況下,程序?qū)?br>執(zhí)行try語(yǔ)句塊中的所有代碼,知道發(fā)生異常為止。此時(shí),將跳過try語(yǔ)句塊中的剩余代碼,轉(zhuǎn)去
執(zhí)行與該異常匹配的catch子句中的代碼,最后執(zhí)行finally子句中的代碼。

如果catch子句沒有拋出異常,程序?qū)?zhí)行try語(yǔ)句塊之后的第一條語(yǔ)句,在這里,執(zhí)行順序:1,3,4,5,6

如果catch子句拋出了一個(gè)異常,異常江北拋回這個(gè)方法的調(diào)用者。在這里,執(zhí)行順序是:1,3,5
3. 代碼拋出了一個(gè)異常,但這個(gè)異常不是由catch子句捕獲的。在這種情況下,程序?qū)?zhí)行try語(yǔ)句塊中的所有
語(yǔ)句,知道有異常被拋出位置。此時(shí)將跳過try語(yǔ)句塊中的剩余代碼,然后執(zhí)行finally子句中的語(yǔ)句,并將異常拋給
這個(gè)方法的調(diào)用者,在這里,執(zhí)行順序:1,5
強(qiáng)烈建議

強(qiáng)烈建議解耦合try/catch和try/finally語(yǔ)句塊。這樣可以提高代碼的清晰度。例如:

   FileInputStream in = new FileInputStream("test.txt");
        try {
            try {
//        code that might throw exception
            } finally {
                in.close();
            }
        } catch (IOException e) {
            //show error message
        }


內(nèi)層的try語(yǔ)句塊只有一個(gè)職責(zé),就是確保關(guān)閉輸入流。外層的try語(yǔ)句塊也只有一個(gè)職責(zé),就是確保
報(bào)告出現(xiàn)的錯(cuò)誤。這種設(shè)計(jì)方式不僅清楚,而且還具有一個(gè)功能,就是將會(huì)報(bào)告finally子句中出現(xiàn)的錯(cuò)誤。
finally子句中也有返回的情況

public static void main(String[] args) {
        System.out.println("n=1時(shí)方法mult返回結(jié)果:" + mult(1));

        System.out.println("n=2時(shí)方法mult返回結(jié)果:" + mult(2));
    }

    public static int mult(int n) {
        try {
            int r = n * n;
            return r;
        } finally {
            if (n == 2) {
                return 0;
            }
        }
    }



得到結(jié)果是:n=1時(shí)方法mult返回結(jié)果:1
n=2時(shí)方法mult返回結(jié)果:0
如結(jié)果所示,當(dāng)n=2時(shí),try語(yǔ)句塊的計(jì)算結(jié)果為r=4,并執(zhí)行return語(yǔ)句,然而,在方法真正返回值之前,還要執(zhí)行finally子句。finally子句將使得方法的返回值為0,這個(gè)返回值會(huì)覆蓋原始的返回值4。

### 帶資源的try語(yǔ)句
對(duì)于以下代碼模式:

 open a resource
  try{
    work with resource
  }
  finally{
     close the resource
  }



假設(shè)資源屬于一個(gè)實(shí)現(xiàn)了AutoCloseable接口的類。Java SE 7為這種代碼提高了一個(gè)很有用的快捷方式,
AutoCloseable 接口有一個(gè)方法。
我們可以簡(jiǎn)寫成

  try(Resource res=...){
    work with res
  }

  例如:
  try(InputStream inputStream = request.getInputStream()){
              reqJsonStr = StreamUtils.copyToString(inputStream , Charset.defaultCharset());
          }

### 分析堆棧軌跡元素
堆棧軌跡(stack trace)是一個(gè)方法調(diào)用過程的列表,它包含了程序執(zhí)行過程中方法調(diào)用的特定位置。
前面已經(jīng)看到過這種列表,當(dāng)Java程序正常終止,而沒有捕獲異常時(shí),這個(gè)列表就會(huì)顯示出來。
e.printStackTrace(); 打印堆棧信息。
例如:以下打印遞歸階乘函數(shù)的堆棧情況

  public static void main(String[] args) {
         Scanner scanner = new Scanner(System.in);
         System.out.println("Enter=" + scanner);
         int n = scanner.nextInt();
         factorial(n);
     }

     public static int factorial(int n) {

         System.out.println("factorial(" + n + ")");
         Throwable throwable = new Throwable();
         StackTraceElement[] stackTrace = throwable.getStackTrace();
         for (StackTraceElement stackTraceElement : stackTrace) {
             System.out.println(stackTraceElement);
         }
         int result;
         if (n == 1) {
             result = 1;
         } else {
             result = n * factorial(n - 1);
         }
         System.out.println("result=" + result);
         return result;
     }



結(jié)果是:

  factorial(3)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
    com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
    factorial(2)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
    com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
    factorial(1)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
    com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
    result=1
    result=2
    result=6



使用異常機(jī)制的技巧

    異常處理不能代替簡(jiǎn)單的測(cè)試
    不要過分地細(xì)化異常

    利用異常層次結(jié)構(gòu)

    不要只拋出RuntimeException異常。應(yīng)該尋找更加適當(dāng)?shù)淖宇惢蛘邉?chuàng)建自己的異常類

    不要只捕獲Throwable異常,否則,會(huì)使程序代碼更難讀,更難維護(hù)。

    不要壓制異常

    在檢測(cè)錯(cuò)誤時(shí)?!笨量獭?要比放任更好

    當(dāng)用無效參數(shù)滴啊用一個(gè)方法時(shí),返回一個(gè)虛擬的數(shù)值,還是拋出一個(gè)異常,哪種處理方式更好呢?
    例如:當(dāng)棧為空時(shí),Stack.pop是返回一個(gè)null,還是拋出一個(gè)異常?我們認(rèn)為:在出錯(cuò)的地方拋出一個(gè)EmptyStackExceptin
    異常要比后面拋出一個(gè)NullPointExceptin異常更好。

    不要羞于傳遞異常

    很多程序員都感覺應(yīng)該捕獲拋出的全部異常。如果調(diào)用了一個(gè)拋出異常的方法,例如FileInputStream構(gòu)造器或
    readLine方法,這些方法就會(huì)本能地捕獲這些可能產(chǎn)生的異常。其實(shí),傳遞異常要比捕獲這些異常更好:

      public void readStuff(String filenam) throws IOException{
            InputStream in=new InputStream(filenam);
            ...
      }  


    讓高層次的方法通知用戶發(fā)生了錯(cuò)誤,或者放棄不成功的命令更加適宜。

    PS: 規(guī)則5,6可以歸納為”早拋出,晚捕獲”,拋出不能處理的異常,捕獲可以處理的異常
    使用斷言
    斷言的概念

    假設(shè)確信某個(gè)屬性符合要求,并且代碼的執(zhí)行依賴于這個(gè)屬性。
    斷言的關(guān)鍵字是assert,這個(gè)關(guān)鍵字有兩周形式:

    assert 條件:和assert 條件:表達(dá)式;

    這兩種形式都會(huì)對(duì)條件進(jìn)行檢測(cè),如果結(jié)果為false,則拋出一個(gè)AssertionError異常,在第二種形式中,
    表達(dá)式將傳入AssertError的構(gòu)造器,并轉(zhuǎn)換成一個(gè)消息字符串
    啟動(dòng)和禁用斷言

    在默認(rèn)情況下,斷言被禁用??梢栽谶\(yùn)行程序時(shí)用-enableassertions或者-ea選項(xiàng)啟用。
    命令是 java -enableassertions MyApp

    需要注意的是,在啟用或禁用斷言時(shí)不必重新編譯程序。啟用或禁用斷言時(shí) 類加載器的功能,當(dāng)斷言被禁用時(shí),
    類加載器將跳過斷言代碼,因此不會(huì)降低程序的運(yùn)行速度。
    使用斷言完成參數(shù)檢查

    什么時(shí)候應(yīng)該選擇使用斷言呢?請(qǐng)記住下面幾點(diǎn):
    斷言失敗是致命的,不可恢復(fù)的錯(cuò)誤。

    斷言檢查只用于開發(fā)和測(cè)試階段
    記錄日志

    基本日志

    可以使用全局日志記錄器(global logger) 并調(diào)用其info方法。
    Logger.getGlobal().info("日志測(cè)試");
    設(shè)置日志級(jí)別:
    Logger.getGlobal().setLevel(Level.INFO);

    高級(jí)日志

    可以自定義日志記錄器,可以調(diào)用getLogger方法創(chuàng)建或獲取記錄器:

    private static final Logger myLogger=Logger.getLogger("com.jay.exception.LoggerTest");

    未被任何變量引用的日志記錄器可能會(huì)被垃圾回收機(jī)制回收。為了防止這種情況發(fā)生,要像
    上面的例子中一樣,用一個(gè)靜態(tài)變量存儲(chǔ)日志記錄的一個(gè)引用。

    通常,有以下7個(gè)日志記錄器級(jí)別:
        SERVER
        WARNING
        INFO
        CONFIG
        FINE
        FINER
        FINEST
        默認(rèn)情況下,只記錄前三個(gè)級(jí)別。也可以設(shè)置其他的級(jí)別。例如:
        logger.setLevel(Level.FINE)
    修改日志管理器配置
    在默認(rèn)情況下,配置文件存在于 jre/lib/logging.propertiest,要想使用另一個(gè)配置文件,
    就要將 java.util.logging.config.file 特性設(shè)置為配置文件的存儲(chǔ)位置。并用下列命令啟動(dòng)應(yīng)用程序
    java -Djava.util.logging.config.file=configFile MainClass





作者:碼農(nóng)飛哥
微信公眾號(hào):碼農(nóng)飛哥