1. 程式人生 > 實用技巧 >第四章:異常處理

第四章:異常處理

一、異常體系結構

異常的分類:

從程式執行過程來看,分為編譯時異常和執行時異常。

編譯時異常:執行javac.exe命令時,可能出現的異常。

執行時異常:執行java.exe命令時,出現的異常。

程式碼:

package com.zixue.java;

/*
 * Error:
 * Java虛擬機器無法解決的嚴重問題。如:JVM系統內部錯誤、資源耗盡等嚴重情況。
 * 比如:StackOverflowError和OOM。
 * 
 * 一般不編寫針對性的程式碼進行處理。
 */
public class ErrorTest {
    public static void main(String[] args) {
        
//1.棧溢位:java.lang.StackOverflowError // main(args); //2.堆溢位:java.lang.OutOfMemoryError Integer[] arr = new Integer[1024*1024*1024]; } }

二、常見的異常

package com.zixue.java;

import org.junit.Test;

import java.util.Date;
import java.util.Scanner;

public class ExceptionTest {
    
    
//******************以下是編譯時異常*************************** @Test public void test7(){ // File file = new File("hello.txt"); // FileInputStream fis = new FileInputStream(file); // // int data = fis.read(); // while(data != -1){ // System.out.print((char)data); // data = fis.read();
// } // // fis.close(); } //******************以下是執行時異常*************************** //ArithmeticException @Test public void test6(){ int a = 10; int b = 0; System.out.println(a / b); } //InputMismatchException @Test public void test5(){ Scanner scanner = new Scanner(System.in); int score = scanner.nextInt(); System.out.println(score); scanner.close(); } //NumberFormatException @Test public void test4(){ String str = "123"; str = "abc"; int num = Integer.parseInt(str); } //ClassCastException @Test public void test3(){ Object obj = new Date(); String str = (String)obj; } //IndexOutOfBoundsException @Test public void test2(){ //ArrayIndexOutOfBoundsException // int[] arr = new int[10]; // System.out.println(arr[10]); //StringIndexOutOfBoundsException String str = "abc"; System.out.println(str.charAt(3)); } //NullPointerException @Test public void test1(){ // int[] arr = null; // System.out.println(arr[3]); String str = "abc"; str = null; System.out.println(str.charAt(0)); } }

三、異常的處理

3.1、異常處理的抓拋模型

過程一:"拋"

  程式在正常執行的過程中,一旦出現異常,就會在異常程式碼處生成一個對應異常類的物件,並將此物件丟擲。一旦丟擲物件以後,其後的程式碼就不再執行。

  關於異常物件的產生:

    ① 系統自動生成的異常物件

    ② 手動的生成一個異常物件,並丟擲(throw)

過程二:"抓"

  可以理解為異常的處理方式:

    ① try-catch-finally

     ② throws

3.2、處理方式一:try-catch-finally

3.2.1、格式

try{

  //可能出現異常的程式碼

}catch(異常型別1 變數名1){

  //處理異常的方式1

}catch(異常型別2 變數名2){

  //處理異常的方式2

}catch(異常型別3 變數名3){

  //處理異常的方式3

}

....

finally{

  //一定會執行的程式碼

}

3.2.2、說明

  ① finally是可選的。

  ② 使用try將可能出現異常的程式碼包裹起來,在執行過程中,一旦出現異常,就會生成一個對應異常類的物件,根據此物件的型別,去catch中進行匹配

  ③ 一旦try中的異常物件匹配到某一個catch時,就進入catch中進行異常的處理。一旦處理完成,就跳出當前的try-catch結構(在沒寫finally的情況。繼續執行其後的程式碼)

  ④ catch中的異常型別如果沒有子父類關係,則誰宣告在上,誰宣告在下無所謂;catch中的異常型別如果滿足子父類關係,則要求子類一定宣告在父類的上面。否則,報錯

  ⑤ 常用的異常物件處理的方式: ① String getMessage() ② printStackTrace()

  ⑥ 在try結構中宣告的變數,在出了try結構以後,就不能再被呼叫

  ⑦ try-catch-finally結構可以巢狀

3.2.3、如何看待程式碼中的編譯時異常和執行時異常?

  體會1:使用try-catch-finally處理編譯時異常,使得程式在編譯時就不再報錯,但是執行時仍可能報錯。相當於我們使用try-catch-finally將一個編譯時可能出現的異常,延遲到執行

時出現。

  體會2:開發中,由於執行時異常比較常見,所以我們通常就不針對執行時異常編寫try-catch-finally了。針對於編譯時異常,我們說一定要考慮異常的處理。

3.2.4、程式碼

package com.zixue.java;

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest1 {
    
    
    @Test
    public void test2(){
        try{
            File file = new File("hello.txt");
            FileInputStream fis = new FileInputStream(file);
            
            int data = fis.read();
            while(data != -1){
                System.out.print((char)data);
                data = fis.read();
            }
            
            fis.close();
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    
    @Test
    public void test1(){
        
        String str = "123";
        str = "abc";
        int num = 0;
        try{
            num = Integer.parseInt(str);
            
            System.out.println("hello-----1");
        }catch(NumberFormatException e){
//            System.out.println("出現數值轉換異常了,不要著急....");
            //String getMessage():
//            System.out.println(e.getMessage());
            //printStackTrace():
            e.printStackTrace();
        }catch(NullPointerException e){
            System.out.println("出現空指標異常了,不要著急....");
        }catch(Exception e){
            System.out.println("出現異常了,不要著急....");
            
        }
        System.out.println(num);
        
        System.out.println("hello-----2");
    }
    
}

3.2.5、finally的使用

  ① finally是可選的

  ② finally中宣告的是一定會被執行的程式碼。即使catch中又出現異常了,try中return語句,catch中return語句等情況。

  ③ 像資料庫連線、輸入輸出流、網路程式設計Socket等資源,JVM是不能自動的回收的,我們需要自己手動的進行資源的釋放。此時的資源釋放,就需要宣告在finally中。

package com.zixue.java;

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FinallyTest {
    
    @Test
    public void test2(){
        FileInputStream fis = null;
        try {
            File file = new File("hello1.txt");
            fis = new FileInputStream(file);
            
            int data = fis.read();
            while(data != -1){
                System.out.print((char)data);
                data = fis.read();
            }
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                if(fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    @Test
    public void testMethod(){
        int num = method();
        System.out.println(num);
    }
    
    public int method(){
        
        try{
            int[] arr = new int[10];
            System.out.println(arr[10]);
            return 1;
        }catch(ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
            return 2;
        }finally{
            System.out.println("我一定會被執行");
            return 3;
        }
    }
    
    @Test
    public void test1(){
        try{
            int a = 10;
            int b = 0;
            System.out.println(a / b);
            
        }catch(ArithmeticException e){
            e.printStackTrace();
            
//            int[] arr = new int[10];
//            System.out.println(arr[10]);
            
        }catch(Exception e){
            e.printStackTrace();
        }
//        System.out.println("我好帥啊!!!~~");
        
        finally{
            System.out.println("我好帥啊~~");
        }
    }
}

3.3、處理方式二:throws + 異常型別

"throws + 異常型別"寫在方法的宣告處。指明此方法執行時,可能會丟擲的異常型別。一旦當方法體執行時,出現異常,仍會在異常程式碼處生成一個異常類的物件,此物件滿足throws後異常型別時,就會被丟擲。異常程式碼後續的程式碼,就不再執行!

3.4、兩種處理方式的對比

  try-catch-finally:真正的將異常給處理掉了。

  throws的方式只是將異常拋給了方法的呼叫者。並沒真正將異常處理掉。

3.5、開發中應該如何選擇兩種處理方式?

  ① 如果父類中被重寫的方法沒有通過throws的方式處理異常,則子類重寫的方法也不能使用throws,意味著如果子類重寫的方法中異常,必須使用try-catch-finally方式處理。

  ② 執行的方法a中,先後又呼叫了另外的幾個方法,這幾個方法是遞進關係執行的。我們建議這幾個方法使用throws的方式進行處理。而執行的方法a可以考慮使用try-catch-finally方式進行處理。

package com.zixue.java;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest2 {
    
    
    public static void main(String[] args){
        try{
            method2();
            
        }catch(IOException e){
            e.printStackTrace();
        }
        
//        method3();
        
    }
    
    
    public static void method3(){
        try {
            method2();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    
    public static void method2() throws IOException{
        method1();
    }
    
    
    public static void method1() throws FileNotFoundException,IOException{
        File file = new File("hello1.txt");
        FileInputStream fis = new FileInputStream(file);
        
        int data = fis.read();
        while(data != -1){
            System.out.print((char)data);
            data = fis.read();
        }
        
        fis.close();
        
        System.out.println("hahaha!");
    }
    
    
}

3.6、關於方法重寫的補充

package com.zixue.java;

import java.io.FileNotFoundException;
import java.io.IOException;

/*
 * 方法重寫的規則之一:
 * 子類重寫的方法丟擲的異常型別不大於父類被重寫的方法丟擲的異常型別
 * 
 * 
 */
public class OverrideTest {
    
    public static void main(String[] args) {
        OverrideTest test = new OverrideTest();
        test.display(new SubClass());
    }

    
    public void display(SuperClass s){
        try {
            s.method();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class SuperClass{
    
    public void method() throws IOException{
        
    }
}

class SubClass extends SuperClass{
    public void method()throws FileNotFoundException {
        
    }
}

四、手動丟擲異常

4.1、使用說明

在程式執行中,除了自動丟擲異常物件的情況之外,我們還可以手動的throw一個異常類的物件。

4.2、面試題

throw 和 throws的區別?

  throw 表示丟擲一個異常類的物件,生成異常物件的過程。宣告在方法體內。

  throws 屬於異常處理的一種方式,宣告在方法的宣告處。

4.3、程式碼

package com.zixue.java2;

public class StudentTest {
    
    public static void main(String[] args) {
        try {
            Student s = new Student();
            s.regist(-1001);
            System.out.println(s);
        } catch (Exception e) {
//            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}

class Student{
    
    private int id;
    
    public void regist(int id) throws Exception {
        if(id > 0){
            this.id = id;
        }else{
//            System.out.println("您輸入的資料非法!");
            //手動丟擲異常物件
//            throw new RuntimeException("您輸入的資料非法!");
//            throw new Exception("您輸入的資料非法!");
            throw new MyException("不能輸入負數");
            //錯誤的
//            throw new String("不能輸入負數");
        }
        
    }

    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }
}

package com.zixue.java2;

public class ReturnExceptionDemo {
    static void methodA() {
        try {
            System.out.println("進入方法A");
            throw new RuntimeException("製造異常");
        } finally {
            System.out.println("用A方法的finally");
        }
    }

    static void methodB() {
        try {
            System.out.println("進入方法B");
            return;
        } finally {
            System.out.println("呼叫B方法的finally");
        }
    }

    public static void main(String[] args) {
        try {
            methodA();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        methodB();
    }
}

五、自定義異常類

5.1、如何自定義異常類?

  ① 繼承於現有的異常結構:RuntimeException 、Exception

  ② 提供全域性常量:serialVersionUID

  ③ 提供過載的構造器

5.2、示例

package com.zixue.java2;

/*
 * 如何自定義異常類?
 * 1. 繼承於現有的異常結構:RuntimeException 、Exception
 * 2. 提供全域性常量:serialVersionUID
 * 3. 提供過載的構造器
 * 
 */
public class MyException extends Exception{
    
    static final long serialVersionUID = -7034897193246939L;
    
    public MyException(){
        
    }
    
    public MyException(String msg){
        super(msg);
    }
}

六、綜合練習題

答案:

package com.zixue.exer;

//自定義異常類
public class EcDef extends Exception {

    static final long serialVersionUID = -33875164229948L;

    public EcDef() {
    }

    public EcDef(String msg) {
        super(msg);
    }
}
package com.zixue.exer;

public class EcmDef {
    public static void main(String[] args) {
        try{
            int i = Integer.parseInt(args[0]);
            int j = Integer.parseInt(args[1]);
            
            int result = ecm(i,j);
            
            System.out.println(result);
        }catch(NumberFormatException e){
            System.out.println("資料型別不一致");
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println("缺少命令列引數");
        }catch(ArithmeticException e){
            System.out.println("除0");
        }catch(EcDef e){
            System.out.println(e.getMessage());
        }
        
    }
    
    public static int ecm(int i,int j) throws EcDef{
        if(i < 0 || j < 0){
            throw new EcDef("分子或分母為負數了!");
        }
        return i / j;
    }
}