今天,咱們將討論一個很是重要的主題-Java 中的異常處理。儘管有時可能會對此主題進行過多的討論,但並不是每篇文章都包含有用且相關的信息。java
Java 中最多見的異常處理機制一般與 try-catch 塊關聯 。咱們使用它來捕獲異常,而後提供在發生異常的狀況下能夠執行的邏輯。數據庫
的確,你不須要將全部異常都放在這些塊中。另外一方面,若是你正在研究應用程序的軟件設計,則可能不須要內置的異常處理機制。在這種狀況下,你能夠嘗試使用替代方法-Vavr Try 結構。數組
在本文中,咱們將探討 Java 異常處理的不一樣方法,並討論如何使用 Vavr Try 替代內置方法。讓咱們開始吧!函數
做爲介紹,讓咱們回顧一下 Java 如何容許咱們處理異常。若是你不記得它,則 Java 中的異常會指出意外或意外事件,該異常在程序執行期間(即在運行時)發生,這會破壞程序指令的正常流程。Java爲咱們提供了上述 try-catch 捕獲異常的機制。讓咱們簡要檢查一下它是如何工做的。測試
首先,讓咱們看一個很是常見的例子。這是一個包含 JDBC 代碼的代碼段:編碼
Connection connection = dataSource.getConnection(); String updateNameSql = "UPDATE employees SET name=? WHERE emp_id=?"; PreparedStatement preparedStatement = connection.prepareStatement(updateNameSql);
坦白地說,你的 IDE 甚至不容許你編寫這段代碼,而是要求用 try-catch 塊將其包圍,像這樣:設計
try { Connection connection = dataSource.getConnection(); String updateNameSql = "UPDATE employees SET name=? WHERE emp_id=?"; PreparedStatement preparedStatement = connection.prepareStatement(updateNameSql); } catch (SQLException ex){ }
注:咱們能夠將其重構爲 try-with-resources,可是稍後再討論。code
那麼,爲何咱們要這樣編寫代碼?由於 SQLException 是一個檢查異常。orm
若是這些異常能夠由方法或構造函數的執行拋出並傳播到方法或構造函數邊界以外,則必須在方法或構造函數的 throws 子句中聲明這些異常。SQLException 若是發生數據庫訪問錯誤,則在示例中使用的方法將拋出 。所以,咱們用一個 try-catch 塊將其包圍。blog
Java 在編譯過程當中驗證了這些異常,這就是它們與運行時異常不一樣的緣由。
可是,並不是每一個異常都應被一個 try-catch 塊包圍。
Java 異常是 Throwable 的子類,可是其中一些是 RuntimeException 類的子類。看下面的圖,它給出了 Java 異常的層次結構:
請注意,運行時異常是特定的組。根據 Java 規範,從這些異常中仍是有可能恢復的。做爲示例,讓咱們回想一下 ArrayIndexOutOfBoundsException。看看下面的示例代碼片斷:
int numbers[] = [1,43,51,0,9]; System.out.println(numbers[6]);
在這裏,咱們有一個具備5個值(0-4位)的整數數組。當咱們嘗試檢索絕對超出範圍的值(索引= 6)時,Java 將拋出 ArrayIndexOutOfBoundsException。
這代表咱們嘗試調用的索引爲負數,大於或等於數組的大小。如我所說,這些異常能夠修復,所以在編譯過程當中不會對其進行檢查。這意味着你仍然能夠編寫以下代碼:
int numbers[] = [1,43,51,0,9]; int index = 6; try{ System.out.println(numbers[index]); } catch (ArrayIndexOutOfBoundsException ex){ System.out.println("Incorrect index!"); }
可是你沒必要這樣作。
Error 是另外一個棘手的概念。再看一下上面的圖-存在錯誤,可是一般不會處理。爲何?一般,這是因爲 Java 程序沒法執行任何操做來從錯誤中恢復,例如:錯誤代表嚴重的問題,而合理的應用程序甚至不該嘗試捕獲。
讓咱們考慮一下內存錯誤– java.lang.VirtualMachineError。此錯誤代表 JVM 已損壞或已經用盡了繼續運行所必需的資源。換句話說,若是應用程序的內存不足,則它根本沒法分配額外的內存資源。
固然,若是因爲持有大量應釋放的內存而致使失敗,則異常處理程序能夠嘗試釋放它(不是直接釋放它自己,而是能夠調用JVM來釋放它)。而且,儘管這樣的處理程序在這種狀況下可能有用,可是這樣的嘗試可能不會成功。
上述編寫 try-catch 語句的方法並非 Java 中惟一可用的方法。還有其餘方法:try-with-resources,try-catch-finally 和多個 catch 塊。讓咱們快速瀏覽這些不一樣的方法。
try-with-resources 塊在 Java 7 中引入的,並容許開發者在程序運行到此結束後必須關閉聲明的資源。咱們能夠在實現該 AutoCloseable 接口(即特定標記接口)的任何類中包含資源。咱們能夠像這樣重構所提到的 JDBC 代碼:
try (Connection connection = dataSource.getConnection){ String updateNameSql = "UPDATE employees SET name=? WHERE emp_id=?"; PreparedStatement preparedStatement = connection.prepareStatement(updateNameSql); } catch (SQLException ex){ //.. }
Java 確保咱們 Connection 在執行代碼後將其關閉。在進行此構建以前,咱們必須顯式地關閉 finally 塊中的資源。
finally 塊在任何狀況下都將執行。例如在成功狀況下或在異常狀況下。在其中,你須要放置將在以後執行的代碼:
FileReader reader = null; try { reader = new FileReader("/text.txt"); int i=0; while(i != -1){ i = reader.read(); System.out.println((char) i); } } catch(IOException ex1){ //... } finally{ if(reader != null){ try { reader.close(); } catch (IOException ex2) { //... } } }
請注意,此方法有一個缺點:若是在 finally 塊內引起異常 ,則會使其中斷。所以,咱們必須正常處理異常。將 try-with-resources 與可關閉的資源一塊兒使用,避免在 finally 塊內關閉資源 。
最後,Java 容許咱們使用一個 try-catch 塊屢次捕獲異常。當方法拋出幾種類型的異常而且您想區分每種狀況的邏輯時,這頗有用。舉個例子,讓這個虛構的類使用拋出多個異常的方法:
class Repository{ void insert(Car car) throws DatabaseAccessException, InvalidInputException { //... } } //... try { repository.insert(car); } catch (DatabaseAccessException dae){ System.out.println("Database is down!"); } catch (InvalidInputException iie){ System.out.println("Invalid format of car!"); }
在這裏須要記住什麼?一般,咱們假設在此代碼中,這些異常處於同一級別。可是你必須從最具體到最通常的順序排序 catch 塊。例如,捕獲 ArithmeticException 異常必須在 捕獲 Exception 異常以前。
到這裏,咱們已經回顧瞭如何使用內置方法處理 Java 中的異常。如今,讓咱們看一下如何使用 Vavr 庫執行此操做。
咱們回顧了捕獲 Java 異常的標準方法。另外一種方法是使用 Vavr Try 類,Vavr 是 Java 8+ 中一個函數式庫,提供了一些不可變數據類型及函數式控制結構。首先,添加 Vavr 庫依賴項:
<dependency> <groupId>io.vavr</groupId> <artifactId>vavr</artifactId> <version>0.10.2</version> </dependency>
Vavr 包含的 Try 類是 monadic 容器類型,它表示可能致使異常或返回成功計算出的值的計算。此結果能夠採用 Success 或 Failure。看下面這段代碼:
class CarsRepository{ Car insert(Car car) throws DatabaseAccessException { //... } Car find (String id) throws DatabaseAccessException { //.. } void update (Car car) throws DatabaseAccessException { //.. } void remove (String id) throws DatabaseAccessException { //.. } }
在調用此代碼時,咱們將使用這些 try-catch 塊來處理 DatabaseAccessException。可是另外一個解決方案是使用 Vavr 對其進行重構。查看如下代碼片斷:
class CarsVavrRepository{ Try<Car> insert(Car car) { System.out.println("Insert a car..."); return Try.success(car); } Try<Car> find (String id) { System.out.println("Finding a car..."); System.out.println("..something wrong with database!"); return Try.failure(new DatabaseAccessException()); } Try<Car> update (Car car) { System.out.println("Updating a car..."); return Try.success(car); } Try<Void> remove (String id) { System.out.println("Removing a car..."); System.out.println("..something wrong with database!"); return Try.failure(new DatabaseAccessException()); } }
如今,咱們可使用 Vavr 處理數據庫問題。
當咱們收到成功計算的結果時,咱們會收到 Success:
@Test void successTest(){ CarsVavrRepository repository = new CarsVavrRepository(); Car skoda = new Car("skoda", "9T4 4242", "black"); Car result = repository.insert(skoda).getOrElse(new Car("volkswagen", "3E2 1222", "red")); Assertions.assertEquals(skoda.getColor(), result.getColor()); Assertions.assertEquals(skoda.getId(), result.getId()); }
請注意,Vavr.Try 相較於 Vavr.Option,爲咱們提供了一種方便的 getOrElse 方法,在發生故障的狀況下咱們可使用默認值,咱們能夠將這種邏輯與有問題的方法結合使用,例如與 find 一塊兒使用。
在另外一種狀況下,咱們將處理 Failure:
@Test void failureTest(){ CarsVavrRepository repository = new CarsVavrRepository(); // desired car Car bmw = new Car("bmw", "4A1 2019", "white"); // failure car Car failureCar = new Car("seat", "1A1 3112", "yellow"); Car result = repository.find("4A1 2019").getOrElse(failureCar); Assertions.assertEquals(bmw.getColor(), result.getColor()); Assertions.assertEquals(bmw.getId(), result.getId()); }
運行此代碼。因爲斷言錯誤,該測試將失敗:
org.opentest4j.AssertionFailedError: Expected :white Actual :yellow
這意味着由於咱們在 find 方法中對失敗進行了硬編碼 ,因此咱們收到了默認值。除了返回默認值以外,咱們還能夠在發生錯誤的狀況下執行其餘操做並生成結果。你可使用連接函數 Option 來使您的代碼更具功能性:
repository.insert(bmw).andThen(car -> { System.out.println("Car is found "+car.getId()); }).andFinally(()->{ System.out.println("Finishing"); });
或者,你可使用收到的異常執行代碼,以下所示:
repository.find("1A9 4312").orElseRun(error->{ //... });
通常來講,Vavr Try 是一個功能豐富的解決方案,可用於以更實用的方式轉換代碼庫。毫無疑問,它與其餘 Vavr 類(如 Option 或 Collections)結合後,才能夠釋放出真正的力量。可是, 若是您想編寫更多的功能樣式的代碼,即便沒有它們,Vavr Try 對於 Java 的 try-catch 塊來講也是一個的正確的替代選擇。
Java 中的異常處理機制一般與 try-catch 塊關聯, 以便捕獲異常並提供發生異常時將要執行的邏輯。一樣,咱們確實不須要將全部異常都放入這些塊中。在本文中,咱們探討了如何使用 Vavr 庫執行此操做。