《On Java 8》第15章 異常

Java 的基本理念是“結構不佳的代碼不能運行”。

Java 使用異常來提供一致的錯誤報告模型,使得構件能夠與客戶端代碼可靠地溝通問題。

Java 異常體系結構圖

在這裏插入圖片描述

異常概念

C 以及其他早期語言常常具有多種錯誤處理模式,這些模式往往建立在約定成俗的基礎上,而並不是語言的一部分。

Java 引入異常?

在異常處理程序中,不僅節省代碼,而且把“描述在正常執行過程中做什麼事”的代碼和“出了問題怎麼辦”的代碼相分離。與之前的錯誤處理方法相比,異常機制使代碼的閱讀、編寫和調試工作更加井井有條。

基本異常

異常拋出後:

1、使用 new 在堆上創建異常對象

2、當前的執行路徑被終止

3、從當前環境中彈出對異常對象的引用

4、異常處理機制接管程序,在異常處理程序中繼續執行程序

自定義異常

class SimpleException extends Exception {
    SimpleException(String message) {
        super(message);
    }
}

public class Test {
    public void method() throws SimpleException {
        throw new SimpleException("hehe");
    }

    public static void main(String[] args) {
        Test test = new Test();
        try {
            test.method();
        } catch (Exception e) {
            e.printStackTrace();
            // e.printStackTrace(System.out); // 標準輸出流
            // e.printStackTrace(System.err); // 標準錯誤流
        }
    }
}

// 運行結果
SimpleException: hehe

異常聲明

Java 強制使用異常聲明,使你能以禮貌的方式告知客戶端程序員某個方法可能會拋出的異常類型,然後客戶端程序員就可以進行相應的處理。

異常聲明屬於方法聲明的一部分,緊跟在形式參數列表之後。

void method() throws MyException, MyException1, MyException2 {}

可以聲明方法將拋出異常,實際上卻不拋出。這樣做的好處是,爲異常先佔個位子,以後就可以拋出這種異常而不用修改已有的代碼,尤其在定義抽象基類和接口時會用到。

重新拋出異常

調用 fillInStackTrace() 的那一行會成爲異常的新發生地。

public class Rethrowing {
    public static void method() throws Exception {
        throw new Exception();
    }

    public static void main(String[] args) {
        try {
            method();
        } catch (Exception e) {
            // e.fillInStackTrace();
            e.printStackTrace();
        }
    }
}

// 運行結果1:
java.lang.Exception
	at Rethrowing.method(Rethrowing.java:3)
	at Rethrowing.main(Rethrowing.java:8)
    
// 運行結果2:
java.lang.Exception
	at Rethrowing.main(Rethrowing.java:10)

異常鏈

常常會想要在捕獲一個異常後拋出另一個異常,並且希望把原始異常的信息保存下來,這被稱爲異常鏈。

JDK1.4 之後,所有的 Throwable 的子類在構造器中都可以接受一個 cause 對象作爲參數,這個 cause 就用來表示原始異常。

// 方法一:
RuntimeException runtimeException = new RuntimeException(new NullPointerException());

// 方法二:
RuntimeException runtimeException = new RuntimeException().initCause(new NullPointerException());

Try-With-Resources 用法

// 方法一:
public class Test {
    public static void main(String[] args) {
        InputStream in = null;
        try {
            in = new FileInputStream(new File("haha.txt"));
            int contents = in.read();
            // Process contents
        } catch (IOException e) {
            // Handle the error
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    // Handle the close() error
                }
            }
        }
    }
}

// 方法二:
public class Test {
    public static void main(String[] args) {
        // 資源規範頭
        try (InputStream in = new FileInputStream(new File("haha.txt"))) {
            int contents = in.read();
            // Process contents
        } catch (IOException e) {
            // Handle the error
        }
    }
}

1、在 try-with-resources 定義子句中創建的對象必須實現 java.lang.AutoCloseable 接口(Closeable 繼承於 AutoCloseable),這個接口有一個方法:close()。否則,會編譯器報錯。

2、資源規範頭中可以包含多個定義,並且通過分號進行分割(最後一個分號是可選的)。規範頭中定義的每個對象都會在 try 語句塊結束之後調用 close() 方法,關閉順序與創建順序相反。

3、try-with-resources 裏面的 try 語句塊可以不包含 catch 或者 finally 語句而獨立存在。

class A implements AutoCloseable {
    String name = getClass().getSimpleName();

    A() {
        System.out.println("Creating " + name);
    }

    public void close() throws Exception {
        System.out.println("Closing " + name);
    }
}

class B extends A {
    @Override
    public void close() throws Exception {
        super.close();
        throw new Exception();
    }
}

public class CloseExceptions {
    public static void main(String[] args) {
        try (
                A a = new A();
                B b = new B();
        ) {
            System.out.println("In body");
        } catch (Exception e) {
            System.out.println("Caught: " + e);
        }
    }
}

// 運行結果:
Creating A
Creating B
In body
Closing B
Closing A
Caught: java.lang.Exception

獲取線程堆棧信息

Thread.getAllStackTraces()
Thread.currentThread().getStackTrace()
new Exception().getStackTrace()
new Throwable().getStackTrace()
    
StackTraceElement[]
    
public final class StackTraceElement implements java.io.Serializable {
    // Normally initialized by VM (public constructor added in 1.5)
    private String declaringClass;
    private String methodName;
    private String fileName;
    private int    lineNumber;

異步調用堆棧信息跟蹤

import java.util.Map;
import java.util.concurrent.CompletableFuture;

public class TurnOffChecking {
    public static void main(String[] args) {
        CompletableFuture<Void> run = CompletableFuture.runAsync(() -> {
            process();
        });

        new Thread() {
            @Override
            public void run() {
                Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
                for (Map.Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet()) {
                    System.out.println("*****" + entry.getKey());
                    for (StackTraceElement element : entry.getValue()) {
                        System.out.println(element);
                    }
                }
            }
        }.run();
    }

    public static void process() {
        while (true) {
            try {
                Thread.sleep(100000);
                break;
            } catch (Exception e) {
            }
        }
    }
}

// 運行結果:
*****Thread[ForkJoinPool.commonPool-worker-1,5,main]
java.lang.Thread.sleep(Native Method)
TurnOffChecking.process(TurnOffChecking.java:27)
TurnOffChecking.lambda$main$0(TurnOffChecking.java:7)
TurnOffChecking$$Lambda$1/1324119927.run(Unknown Source)
java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1626)
java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1618)
java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
*****Thread[Reference Handler,10,system]
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:502)
java.lang.ref.Reference.tryHandlePending(Reference.java:191)
java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
*****Thread[Signal Dispatcher,9,system]
*****Thread[Monitor Ctrl-Break,5,main]
java.net.SocketInputStream.socketRead0(Native Method)
java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
java.net.SocketInputStream.read(SocketInputStream.java:171)
java.net.SocketInputStream.read(SocketInputStream.java:141)
sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
java.io.InputStreamReader.read(InputStreamReader.java:184)
java.io.BufferedReader.fill(BufferedReader.java:161)
java.io.BufferedReader.readLine(BufferedReader.java:324)
java.io.BufferedReader.readLine(BufferedReader.java:389)
com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
*****Thread[Attach Listener,5,system]
*****Thread[main,5,main]
java.lang.Thread.dumpThreads(Native Method)
java.lang.Thread.getAllStackTraces(Thread.java:1610)
TurnOffChecking$1.run(TurnOffChecking.java:13)
TurnOffChecking.main(TurnOffChecking.java:21)
*****Thread[Finalizer,8,system]
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章