java異常機制全介紹

java異常機制

異常的基本概念

什麼是異常

  • 在程序運行中出現的錯誤,稱爲異常

  • 異常模擬的是現實世界中不正常的事件

  • java中採用的方式實現異常(和其他的類是一樣的,按照類和對象理解就可以啦)

    • 類是可以創建對象的

      NullPointerException e = 0x1234;
      
    • e 是引用類型,e 中保存的內存地址指向堆中的異常對象

    • 這個對象一定是NullPointerException類型的異常

    • e 表示真實存在的NullPointerException類型的異常“對象”

      異常都是一類一類的

  • 異常舉例

  ArithmeticException 算術異常(divide by zero)
  ArrayIndexOutOfBoundsException 數組越界;
  ArrayStoreException 向數組對象中存儲錯誤類型的數據
  ClassCastException 類型轉換異常 是一類異常
  ClassNotFoundException 找不到類異常
  CloneNotSupportedException 克隆不支持異常
      表示調用了Object類中的clone方法來創建一個對象,但是對象的類中沒有實現Cloneable接口
  IndexOutOfBoundsException 越界異常
  InstantiationException 實例化異常
      嘗試創建一個不能實例化的類的異常,例如實例化了一個接口,抽象方法,數組類,原始數據類型;
  NegativeArraySizeException 負的數組大小異常
      嘗試創建負數長度的數組的異常;
  NoSuchFieldException 訪問類中並不存在的成員拋出的異常;
  NoSuchMethodException 訪問類中並不存在的方法拋出的異常;
  NullPointerException 空指針異常;
  NumberFormatException 在將字符串類型轉爲數字類型時,字符串格式不正確的異常;
  SecurityException 操作不安全的異常;
  StringIndexOutOfBoundsException 由String類中的方法拋出的“字符串越界異常”,一般是`charAt`方法引起的;
  UnsupportedOperationException 請求的操作不支持的異常
  RuntimeException 繼承自 Exception 類,它 和 它的子類都是 unchecked 異常;
  Exception 的直接子類都是 checked 異常, 即編譯時異常,需要提前處理(throws 或者 try...catch...
  • 異常舉例
  public class Test01 {
  	public static void main(String[] args) {
  		int a = 10;
  		int b = 0;
  		int c = a/b;// ArithmeticException
  		
         	// 上面的代碼出現了異常,“沒有處理”,下面的代碼不會執行,直接退出了 JVM
  		System.out.println(c);
  	}
  }
  /*控制檯輸出
  Exception in thread "main" java.lang.ArithmeticException: / by zero
  	at com.test.exception.Test01.main(Test01.java:7)
  */
  • 以上程序編譯通過了,但是運行時出現了異常,表示發生了某一個異常事件。
  • 控制檯輸出信息的本質:
    • 程序在執行過程中發生了ArithmeticException(算術異常)這個事件,JVM爲我們創建了一個ArithmeticException 類型的對象,並且這個對象中包含了詳細的異常信息,並且JVM將這個對象中的信息輸出到控制檯。

異常機制的作用

  • java 語言爲我們提供了一種完善的異常處理機制,作用是:

    程序發生異常事件之後,爲我們輸出詳細的信息,通過這個信息,可以對程序進行一些處理,使得程序更加健壯。

異常的分類&異常繼承結構

異常繼承結構

異常的繼承結構

異常的捕獲和處理

使用throws關鍵字聲明拋出異常

  • 在方法聲明的位置上使用 throws 關鍵字向上拋出異常
    拋出異常的方法

  • 例如

    import java.io.*;
    
    public class Test02 {
    	public static void main(String[] args)  {
    		FileInputStream fs = new FileInputStream("c:\\ab.txt");
    	}
    }
    

在這裏插入圖片描述

  • 以上程序編譯不通過
.\Test02.java:7: 錯誤: 未報告的異常錯誤FileNotFoundException; 必須對其進行捕獲或聲明以便拋出
                FileInputStream fs = new FileInputStream("c:\\ab.txt");
// 爲啥編譯器會檢測出來這個個編譯時異常咧?
因爲FileInputStream 這個構造方法在聲明的位置上使用了 throws FileNotFoundException;
  • 使用 throws 處理異常不是真正的處理異常,而是推卸責任 ;只是一直往上拋並沒有處理

    import java.io.*;
    
    public class Test03 {
    	public static void main(String[] args) throws FileNotFoundException {
    		m1();// m1()方法的聲明位置上使用throws(向上拋)
    		// 上面的m1方法如果出現了異常,因爲採用的是throws上拋給了JVM,JVM遇到這個異常就會退出JVM
    		// 下面這行代碼就不會執行
    		System.out.println("hello world");
    	}
    	static void m1() throws FileNotFoundException{
    		m2();// m2 方法聲明的位置上使用throws(向上拋)
    	}
    	static void m2() throws FileNotFoundException {
    		m3();// m3() 方法的聲明位置上使用 throws(向上拋)
    	}
    	static void m3() throws FileNotFoundException {
    		m4(); // m4方法的聲明位置上使用 throws(向上拋)
    	}
    	static void m4() throws FileNotFoundException{
    		new FileInputStream("c://daf"); // FileInputStream 構造方法聲明位置上使用throws(向上拋)
    	}
    }
    /*
     * 在程序執行過程中出現了 FileNotFoundException類型的異常
     * JVM爲我們創建了一個FileNotFoundException類型的對象
     * 該對象中攜帶了以下信息,JVM負責將這些信息打印到控制檯。
     * 並且JVM停掉了程序的運行。
     
    Exception in thread "main" java.io.FileNotFoundException: c:\daf (系統找不到指定的文件。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(Unknown Source)
    at java.io.FileInputStream.<init>(Unknown Source)
    at java.io.FileInputStream.<init>(Unknown Source)
    at com.test.exception.Test03.m4(Test03.java:20)
    at com.test.exception.Test03.m3(Test03.java:17)
    at com.test.exception.Test03.m2(Test03.java:14)
    at com.test.exception.Test03.m1(Test03.java:11)
    at com.test.exception.Test03.main(Test03.java:8)
    */
    

使用try-catch捕獲處理異常

  • 語法結構

    try{
        // 可能出現異常的代碼
    }catch(異常類型1 變量){
        // 處理異常的代碼
    }catch(異常類型2 變量){
        // 處理異常的代碼
    }...
    
    • catch 語句塊可以寫多個

    • 注意:catch語句塊中從上到下的異常類型必須是從小到大的

      • 例如:

      在這裏插入圖片描述

      try {//可以編譯通過
          FileInputStream fis = new FileInputStream("adnx.txt");//捕獲對應的FileNotFoundException異常
      			fis.read();//捕獲對應的IOException類型的異常
      }catch(FileNotFoundException e) {  //子異常
      
      }catch(IOException e) { // 父異常
      
      }
      try {//編譯報錯
          FileInputStream fis = new FileInputStream("adnx.txt");//捕獲對應的FileNotFoundException異常
      			fis.read();//捕獲對應的IOException類型的異常
      }catch(IOException e) {//父異常
      
      }catch(FileNotFoundException e) {//子異常
      
      }
      
    • try … catch …中最多執行一個 catch 語句塊。執行結束之後 try…catch… 就結束了

  • 例子:

    import java.io.*;
    
    public class Test05 {
    	public static void main(String[] args) {
    		try {
    			// 程序執行到此處發生了FileNotFoundException類型的異常
    			// JVM 會自動創建一個FileNotFoundException 類型的對象,將該對象的內存地址賦值給catch出語句塊中的e變量
    			FileInputStream fis = new FileInputStream("abc");
    			// 上面代碼出現了異常,try語句塊的代碼不再繼續執行,直接進入catch語句塊中執行
    			fis.read();
    		}catch(FileNotFoundException e) {
    			System.out.println("文件不存在異常");
    			// FileNotFoundException 類將Object類中的toString()方法重寫了。
    			System.out.println(e);//java.io.FileNotFoundException: abc (系統找不到指定的文件。)
    		}catch(IOException e){
    			System.out.println("其它IO異常");
    		}
    	}
    }
    
    

getMessage() && printStackTrace()

如何取得異常對象的具體信息,常用的方法有兩種:

  • 取得異常描述信息

    getMessage()

  • 取得異常的堆棧信息(適用於程序調式階段)

    printStackTrace()

舉例:

import java.io.*;

public class Test06 {
	public static void main(String[] args) {
		
		try {
			FileInputStream fis = new FileInputStream("c:/adf.txt");
		} catch (FileNotFoundException e) {
			// 打印堆棧異常信息
			//一般情況下都會使用該方法去調試程序
//			e.printStackTrace();
//			/*輸出如下:
//			 * java.io.FileNotFoundException: c:\adf.txt (系統找不到指定的文件。)
//				at java.io.FileInputStream.open0(Native Method)
//				at java.io.FileInputStream.open(Unknown Source)
//				at java.io.FileInputStream.<init>(Unknown Source)
//				at java.io.FileInputStream.<init>(Unknown Source)
//				at com.test.exception.Test06.main(Test06.java:9)
//			 */
//			System.out.println("abc");
			
			
			// 在發生FileNotFoundException異常的時候,JVM爲我們執行了下面這行代碼,
			//FileNotFoundException e = new FileNotFoundException("c:\\adf.txt (系統找不到指定的文件。)");
			// Throwable 構造方法:
//			public Throwable(String message) {
//		        fillInStackTrace();
//		        detailMessage = message;
//		    }
			// 然後我們在調用e.getMessage()的時候會得到下面的輸出信息
			String msg = e.getMessage();
			System.out.println(msg);
			//輸出 c:\adf.txt (系統找不到指定的文件。)
		}
		
	}
}

finally語句塊詳解

  • finally語句塊可以直接和 try 語句塊連用

    try ... finally...

  • try...catch...finally也可以

  • finally語句塊中的代碼一定會執行的。

    • 測試:

      public class Test07 {
      	public static void main(String[] args) {
      		try {
      			System.out.println("Abc");
      			return;
      		} finally {
      			System.out.println("hhhh");
      		}
      	}
      }
      // 最終輸出
      Abc
      hhhh
      

      可以這麼理解程序的執行順序:

      ​ 先執行第 4 行, 再執行第 7 行 最後再執行第 5 行

    • 再測試

      import java.io.*;
      
      public class Test08 {
      	public static void main(String[] args) throws FileNotFoundException{
      		try {
      			FileInputStream fis = new FileInputStream("c:/ab.txt");
      			// 下面這句不會執行
      			System.out.println("aaaaa");
      		} finally {
      			// 下面這句會執行
      			System.out.println("hhhhh");
      		}
      	}
      }
      /* 輸出結構
      hhhhh
      Exception in thread "main" java.io.FileNotFoundException: c:\ab.txt (系統找不到指定的文件。)
      	at java.io.FileInputStream.open0(Native Method)
      	at java.io.FileInputStream.open(Unknown Source)
      	at java.io.FileInputStream.<init>(Unknown Source)
      	at java.io.FileInputStream.<init>(Unknown Source)
      	at com.test.exception.Test08.main(Test08.java:8)
      */
      

      可以這麼理解程序的執行順序:

      ​ 執行到第 6 行出現異常,先執行第 11 行, 再向上拋出異常,JVM打印出異常信息

    • 再測試(注意

      • 只有這一種情況下,finally 語句塊中的代碼不會被執行
        • 在執行 finally 語句塊之前退出 JVM,則 finally語句塊不會被執行
      public class Test09 {
      	public static void main(String[] args) {
      		try {
      			// 退出jvm
      			System.exit(0);
      		} finally {
      			// 什麼都不會輸出
      			System.out.println("hhhh");
      		}
      	}
      }
      
      

深入 finally 語句塊(面試常考)

  • 舉例01
public class Test10 {
   public static void main(String[] args) {
   	int i = m1();
   	System.out.println(i); //10
   }

   private static int m1() {
   	int i = 10;
   	try {
   		return i;
   	} finally {
   		i++;
   		System.out.println("m()->i: " + i);//11
   	}
   }
}
  • 以上代碼的輸出結果爲:

    m()->i: 11
    10
    

    以上代碼的執行原理:

    public class Test10 {
    	public static void main(String[] args) {
    		int i = m1();
    		System.out.println(i);//10
    	}
    
    	private static int m1() {
    		int i = 10;
    		try {
    			int temp = i; //jvm定義一個臨時變量temp 存儲 i, 然後return的是臨時變量的值
    			return temp;// 所以返回的10
    		} finally {
    			i++;// 針對i操作
    			System.out.println("m()->i: " + i);//11
    		}
    	}
    }
    
  • 02

    finally語句塊是一般來說是一定會執行的,所以通常在程序中爲了保證某資源一定會被釋放,一般寫在finally語句塊中釋放資源

    import java.io.*;
    
    public class Test11 {
    	public static void main(String[] args) {
    		// 必須在外邊聲明,否則finally語句塊中找不到fis
    		FileInputStream fis = null;
    		try {
    			fis = new FileInputStream("Test11.java");
    		} catch (FileNotFoundException e) {
    			e.printStackTrace();
    		}finally {
    			// 爲了保證資源一定會釋放
    			if(fis != null) {
    				try {
    					fis.close();// 釋放資源
    				}catch(IOException e) {// 捕獲異常
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    }
    
    

final && finalize && finally區別

三者沒有任何聯繫

  • final
    • 修飾的類不能被繼承
    • 修飾的方法不能被重寫Override
    • 修飾的實例變量要手動賦值-且不能再修改
    • 修飾的靜態變量是最終的
  • finalize
    • Object類中的方法名
      • 垃圾回收器在回收垃圾之前會調用該方法
        • 一般用於在回收前釋放資源
  • finally
    • 是異常機制中用於聲明最終執行的代碼塊的關鍵字
    • finally語句塊中的代碼總是執行,無論是否發生異常
      • 但是在try中如果執行了System.exit(0),那麼 jvm 就會退出,這個時候finally語句塊中的代碼就不會被執行了。

自定義異常 &手動拋出異常

  1. 假設有如下業務:

    • 用戶在註冊的時候要求用戶名要超過6位,否則就拋出 IllegalNameException 異常
  2. 自定義異常

    自定義異常既可以定義編譯時異常又可以定義運行時異常

    • 定義編譯時異常直接繼承Exception
    • 定義運行時異常直接繼承RuntimeException
  3. 實現

    public class Test12 {
    	public static void main(String[] args) {
    		CustomeService cs = new CustomeService();
    		try {
    			cs.register("jack");// 需要處理異常(要麼拋出,要麼捕獲)
                // jack 小於 6個字符,所以
                /*
                com.test.exception.IllegalNameException: 用戶名長度不能低於6個字符
                at com.test.exception.CustomeService.register(Test12.java:27)
                at com.test.exception.Test12.main(Test12.java:7)
    			*/
    		} catch (IllegalNameException e) {
    			e.printStackTrace();
    		}
    	}
    }
    
    class IllegalNameException extends Exception{//定義編譯時異常
    	//定義異常一般提供兩個構造方法
    	public IllegalNameException() {
    		
    	}
    	
    	public IllegalNameException(String msg) {
    		super(msg);
    	}
    }
    class CustomeService{
    	public void register(String name) throws IllegalNameException{
    		if(name.length() < 6) {
    			IllegalNameException e = new IllegalNameException("用戶名長度不能低於6個字符");
    			throw e; //手動拋出異常//這裏不使用 try...catch... 處理,因爲是要讓用戶知道該異常,所以需要向上拋出
    		}else {
    			//程序能執行到這裏,證明用戶名是合法的
    			System.out.println("註冊成功");
    		}
    	}
    }
    
    • 自定義異常

      • 可以定義編譯時異常或者運行時異常

        • 根據異常出現的概率來決定
        • 然後就可以確定繼承那個類了
          • 繼承Exception是定義編譯時異常
          • 繼承RuntimeException是運行時異常
      • 定義異常的時候一般只要寫兩個構造方法

        public IllegalNameException() {}
        public IllegalNameException(String msg) {
        	super(msg);// 可以用於打印e.message
        }
        
      • 自定義的異常一般在服務端都是向上拋出

        • throws

方法的覆蓋與異常

定律:重寫的方法不能比被重寫的方法拋出更寬泛的異常

  1. 子類無法拋出比父類更多的異常
class A{
    public static void m1(){
        
    }
}
class B extends A{
    @Override
    public static void m1() throws Exception{// 編譯無法通過
        // 子類不能拋出比父類更多的異常
    }
}
  1. 子類無法拋出比父類更高層次的異常

    import java.io.*;
    class A{
        public static void m1() throws FileNotFoundException{
            
        }
    }
    class B extends A{
        @Override
        public static void m1() throws IOException{// 編譯無法通過
            // 子類不能拋出比父類更高層次的異常
        }
    }
    
    import java.io.*;
    class A{
        public static void m1() throws IoException{
            
        }
    }
    class B extends A{
        @Override
        public static void m1() throws FileNotFoundException{// 編譯可以通過
            // 子類可以拋出比父類更低層次的異常
        }
    }
    

以上

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章