面试题答案
一键面试supplyAsync 异常处理
- 未捕获异常默认行为:
supplyAsync
方法返回CompletableFuture<U>
,如果异步任务执行过程中抛出未捕获的异常,该异常会被包装在CompletableFuture
中。当调用get()
等阻塞方法获取结果时,会抛出ExecutionException
,其内部包含原始异常。例如:
当执行CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Test exception"); }).thenApply(result -> { // 这部分不会执行 return result; }).exceptionally(ex -> { // 这部分会处理异常 System.out.println("Caught exception: " + ex.getMessage()); return null; });
get()
方法时,如果没有处理异常,会得到类似如下异常栈:java.util.concurrent.ExecutionException: java.lang.RuntimeException: Test exception at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908) ... Caused by: java.lang.RuntimeException: Test exception ...
- 自定义异常处理逻辑:
- 使用
exceptionally
方法:可以在CompletableFuture
链中使用exceptionally
方法来处理异常。它接收一个Function<Throwable, U>
类型的参数,当异步任务抛出异常时,会调用这个Function
,返回值会作为整个CompletableFuture
的结果。例如:
CompletableFuture.supplyAsync(() -> { if (Math.random() < 0.5) { throw new RuntimeException("Random exception"); } return "Success result"; }).exceptionally(ex -> { System.out.println("Exception handled: " + ex.getMessage()); return "Default value"; }).thenAccept(System.out::println);
- 使用
whenComplete
方法:whenComplete
方法接收一个BiConsumer<? super U,? super Throwable>
类型的参数,无论异步任务正常完成还是抛出异常,都会调用这个BiConsumer
。可以在其中判断是否有异常并进行处理。例如:
CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Test exception"); }).whenComplete((result, ex) -> { if (ex != null) { System.out.println("Caught exception in whenComplete: " + ex.getMessage()); } else { System.out.println("Result: " + result); } });
- 使用
runAsync 异常处理
- 未捕获异常默认行为:
runAsync
方法返回CompletableFuture<Void>
,如果异步任务执行过程中抛出未捕获的异常,默认情况下该异常会被忽略。例如:
这段代码不会打印任何异常信息(在没有设置全局异常处理器等特殊情况下)。CompletableFuture.runAsync(() -> { throw new RuntimeException("Test exception"); });
- 自定义异常处理逻辑:
- 使用
exceptionally
方法:与supplyAsync
类似,可以使用exceptionally
方法处理异常。不过因为runAsync
返回CompletableFuture<Void>
,所以exceptionally
方法的返回值也应该是Void
。例如:
CompletableFuture.runAsync(() -> { if (Math.random() < 0.5) { throw new RuntimeException("Random exception"); } System.out.println("Task completed successfully"); }).exceptionally(ex -> { System.out.println("Exception handled: " + ex.getMessage()); return null; });
- 使用
whenComplete
方法:同样可以使用whenComplete
方法处理异常,在BiConsumer
中判断是否有异常。例如:
CompletableFuture.runAsync(() -> { throw new RuntimeException("Test exception"); }).whenComplete((v, ex) -> { if (ex != null) { System.out.println("Caught exception in whenComplete: " + ex.getMessage()); } else { System.out.println("Task completed successfully"); } });
- 使用
复杂业务场景下两者异常处理方式的选择依据
- 根据返回值需求选择:
- 如果业务场景需要返回结果,使用
supplyAsync
。例如在一个电商系统中,需要异步查询商品库存并返回库存数量,就用supplyAsync
。在异常处理上,使用exceptionally
可以返回一个默认库存值等处理方式,以保证整个业务流程不会因为异常而中断后续依赖库存值的操作。 - 如果业务场景只是执行一个异步操作,不需要返回结果,如异步记录日志、发送通知等,使用
runAsync
。在异常处理上,对于日志记录操作,如果失败,可以在exceptionally
或whenComplete
中记录异常日志,防止因为日志记录失败影响主业务流程。
- 如果业务场景需要返回结果,使用
- 根据异常处理复杂度选择:
- 如果异常处理逻辑比较简单,只是记录异常信息或者返回一个默认值,
exceptionally
方法简洁明了。例如在一个简单的异步文件读取操作(使用supplyAsync
返回文件内容)中,异常时返回空字符串。 - 如果异常处理逻辑复杂,需要根据不同类型的异常做不同处理,可能涉及到资源清理、通知其他系统等操作,
whenComplete
方法更合适。例如在一个异步数据库操作(使用runAsync
)中,不同的数据库异常需要不同的处理方式,在whenComplete
中可以更灵活地编写处理逻辑。
- 如果异常处理逻辑比较简单,只是记录异常信息或者返回一个默认值,