今天,咱们将讨论一个很是重要的主题-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 库执行此操做。