Clojure - 異常處理



任何程式語言都需要異常處理來處理執行時錯誤,以便維護應用程式的正常流程。異常通常會中斷應用程式的正常流程,這就是為什麼我們需要在應用程式中使用異常處理的原因。

異常大致可分為以下幾類:

  • 受檢異常 - 擴充套件 Throwable 類(除了 RuntimeException 和 Error)的類稱為受檢異常。例如 IOException、SQLException 等。受檢異常在編譯時進行檢查。

讓我們考慮以下程式,它對名為 Example.txt 的檔案執行操作。但是,始終可能存在 Example.txt 檔案不存在的情況。

(ns clojure.examples.example
   (:gen-class))

;; This program displays Hello World
(defn Example []
   (def string1 (slurp "Example.txt"))
   (println string1))
(Example)

如果 Example.txt 檔案不存在,則程式將生成以下異常。

Caused by: java.io.FileNotFoundException: Example.txt (No such file or
directory)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at clojure.java.io$fn__9185.invoke(io.clj:229)
at clojure.java.io$fn__9098$G__9091__9105.invoke(io.clj:69)
at clojure.java.io$fn__9197.invoke(io.clj:258)
at clojure.java.io$fn__9098$G__9091__9105.invoke(io.clj:69)

從上面的異常中,我們可以清楚地看到程式引發了 FileNotFoundException。

  • 非受檢異常 - 擴充套件 RuntimeException 的類稱為非受檢異常。例如,ArithmeticException、NullPointerException、ArrayIndexOutOfBoundsException 等。非受檢異常在編譯時不進行檢查,而是在執行時檢查。

一個經典的例子是 ArrayIndexOutOfBoundsException,當您嘗試訪問陣列索引時,該索引大於陣列的長度。以下是此類錯誤的典型示例。

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (aget (int-array [1 2 3]) 5)
      (catch Exception e (println (str "caught exception: " (.toString e))))
      (finally (println "This is our final block")))
   (println "Let's move on"))
(Example)

執行上述程式碼時,將引發以下異常。

caught exception: java.lang.ArrayIndexOutOfBoundsException: 5
This is our final block
Let's move on

錯誤

錯誤是不可恢復的,例如 OutOfMemoryError、VirtualMachineError、AssertionError 等。這些是程式永遠無法恢復的錯誤,會導致程式崩潰。現在我們需要一些機制來捕獲這些異常,以便程式在存在這些異常時能夠繼續執行。

下圖顯示了 Clojure 中異常的層次結構是如何組織的。它全部基於 Java 中定義的層次結構。

Exceptions in Clojure

捕獲異常

與其他程式語言一樣,Clojure 提供了正常的“try-catch”塊來捕獲異常的發生。

以下是 try-catch 塊的一般語法。

(try
   (//Protected code)
   catch Exception e1)
(//Catch block)

所有可能引發異常的程式碼都放置在受保護的程式碼塊中。

catch 塊中,您可以編寫自定義程式碼來處理異常,以便應用程式能夠從異常中恢復。

讓我們看看我們之前的示例,該示例生成了檔案未找到異常,並瞭解如何使用 try catch 塊來捕獲程式引發的異常。

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (def string1 (slurp "Example.txt"))
      (println string1)
      (catch Exception e (println (str "caught exception: " (.getMessage e))))))
(Example)

上述程式產生以下輸出。

caught exception: Example.txt (No such file or directory)

從上面的程式碼中,我們將有問題的程式碼包裝在try 塊中。在 catch 塊中,我們只是捕獲異常並輸出一條訊息,表明發生了異常。因此,我們現在有了一種有意義的方法來捕獲程式生成的異常。

多個 Catch 塊

可以有多個 catch 塊來處理多種型別的異常。對於每個 catch 塊,根據引發的異常型別,您將編寫程式碼來相應地處理它。

讓我們修改我們之前的程式碼,包括兩個 catch 塊,一個特定於我們的檔案未找到異常,另一個是通用異常塊。

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (def string1 (slurp "Example.txt"))
      (println string1)
      
      (catch java.io.FileNotFoundException e (println (str "caught file
         exception: " (.getMessage e))))
      
      (catch Exception e (println (str "caught exception: " (.getMessage e)))))
   (println "Let's move on"))
(Example)

上述程式產生以下輸出。

caught file exception: Example.txt (No such file or directory)
Let's move on

從上面的輸出中,我們可以清楚地看到我們的異常被“FileNotFoundException”catch 塊捕獲,而不是通用 catch 塊。

Finally 塊

finally 塊位於 try 塊或 catch 塊之後。finally 程式碼塊始終執行,無論是否發生異常。

使用 finally 塊允許您執行任何您希望執行的清理型別語句,無論受保護程式碼中發生了什麼。以下是此塊的語法。

(try
   (//Protected code)
   catch Exception e1)
(//Catch block)
(finally
   //Cleanup code)

讓我們修改上面的程式碼並新增 finally 程式碼塊。以下是程式碼片段。

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (def string1 (slurp "Example.txt"))
      (println string1)
      
      (catch java.io.FileNotFoundException e (println (str "caught file
         exception: " (.getMessage e))))
      
      (catch Exception e (println (str "caught exception: " (.getMessage e))))
      (finally (println "This is our final block")))
   (println "Let's move on"))
(Example)

上述程式產生以下輸出。

caught file exception: Example.txt (No such file or directory)
This is our final block
Let's move on

從上面的程式中,您可以看到 finally 塊也在 catch 塊捕獲所需異常之後實現。

由於 Clojure 的異常處理源自 Java,因此與 Java 類似,Clojure 中提供了以下方法來管理異常。

  • public String getMessage() - 返回有關發生的異常的詳細訊息。此訊息在 Throwable 建構函式中初始化。

  • public Throwable getCause() - 返回異常的原因,表示為 Throwable 物件。

  • public String toString() - 返回類的名稱與 getMessage() 的結果連線在一起。

  • public void printStackTrace() - 將 toString() 的結果以及堆疊跟蹤列印到 System.err(錯誤輸出流)。

  • public StackTraceElement [] getStackTrace() - 返回一個包含堆疊跟蹤中每個元素的陣列。索引 0 處的元素表示呼叫堆疊的頂部,陣列中的最後一個元素表示呼叫堆疊底部的 方法。

  • public Throwable fillInStackTrace() - 使用當前堆疊跟蹤填充此 Throwable 物件的堆疊跟蹤,新增到堆疊跟蹤中的任何先前資訊。

以下是使用上面列出的一些方法的示例程式碼。

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (def string1 (slurp "Example.txt"))
      (println string1)
      
      (catch java.io.FileNotFoundException e (println (str "caught file
         exception: " (.toString e))))
      
      (catch Exception e (println (str "caught exception: " (.toString e))))
   (finally (println "This is our final block")))
   (println "Let's move on"))
(Example)

上述程式產生以下輸出。

caught file exception: java.io.FileNotFoundException: Example.txt (No such file
or directory)
This is our final block
Let's move on
廣告