简介
最近发现很多小伙伴还不知道如何在 lambda 表达式中优雅的处理 checked exception,所以今天就重点和大家来探讨一下这个问题。
lambda 表达式本身是为了方便程序员书写方便的工具,使用 lambda 表达式可以让我们的代码更加简洁。
可能大多数小伙伴在使用的过程中从来没有遇到过里面包含异常的情况,所以对这种在 lambda 表达式中异常的处理可能没什么经验。
不过没关系,今天我们就来一起探讨一下。
lambda 表达式中的 checked exception
java 中异常的类型,大家应该是耳熟能详了,具体而言可以有两类,一种是 checked exception, 一种是 unchecked exception。
所谓 checked exception 就是需要在代码中手动捕获的异常。unchecked exception 就是不需要手动捕获的异常,比如运行时异常。
首先我们定义一个 checked exception,直接继承 Exception 就好了:
public class MyCheckedException extends Exception{
@java.io.Serial
private static final long serialVersionUID = -1574710658998033284L;
public MyCheckedException() {
super();
}
public MyCheckedException(String s) {
super(s);
}
}
复制代码
接下来我们定义一个类,这个类中有两个方法,一个抛出 checked exception,一个抛出 unchecked exception:
public class MyStudents {
public int changeAgeWithCheckedException() throws MyCheckedException {
throw new MyCheckedException();
}
public int changeAgeWithUnCheckedException(){
throw new RuntimeException();
}
}
复制代码
好了,我们首先在 lambda 表达式中抛出 CheckedException:
public static void streamWithCheckedException(){
Stream.of(new MyStudents()).map(s->s.changeAgeWithCheckedException()).toList();
}
复制代码
这样写在现代化的 IDE 中是编译不过的,它会提示你需要显示 catch 住 CheckedException,所以我们需要把上面的代码改成下面这种:
public static void streamWithCheckedException(){
Stream.of(new MyStudents()).map(s-> {
try {
return s.changeAgeWithCheckedException();
} catch (MyCheckedException e) {
e.printStackTrace();
}
}).toList();
}
复制代码
这样做是不是就可以了呢?
再考虑一个情况,如果 stream 中不止一个 map 操作,而是多个 map 操作,每个 map 都抛出一个 checkedException,那岂不是要这样写?
public static void streamWithCheckedException(){
Stream.of(new MyStudents()).map(s-> {
try {
return s.changeAgeWithCheckedException();
} catch (MyCheckedException e) {
e.printStackTrace();
}
}).map(s-> {
try {
return s.changeAgeWithCheckedException();
} catch (MyCheckedException e) {
e.printStackTrace();
}
}).
toList();
}
复制代码
实在是太难看了,也不方便书写,那么有没有什么好的方法来处理,lambda 中的 checked 异常呢?办法当然是有的。
lambda 中的 unchecked exception
上面例子中我们抛出了一个 checked exception,那么就必须在 lambda 表达式中对异常进行捕捉。
那么我们可不可以换个思路来考虑一下?
比如,把上面的 checked exception,换成 unchecked exception 会怎么样呢?
public static void streamWithUncheckedException(){
Stream.of(new MyStudents()).map(MyStudents::changeAgeWithUnCheckedException).toList();
}
复制代码
我们可以看到程序可以正常编译通过,可以减少或者几乎不需要使用 try 和 catch,这样看起来,代码是不是简洁很多。
那么我们是不是可以考虑把 checked exception 转换成为 unchecked exception,然后用在 lambda 表达式中,这样就可以简化我们的代码,给程序员以更好的代码可读性呢?
说干就干。
基本的思路就是把传入的 checked exception 转换为 unchecked exception,那么怎么转换比较合适呢?
这里我们可以用到 JDK 中的类型推断,通过使用泛型来达到这样的目的:
public static <T extends Exception,R> R sneakyThrow(Exception t) throws T {
throw (T) t;
}
复制代码
这个方法接收一个 checked exception,在内部强制转换之后,抛出 T。
看看在代码中如何使用:
public static void sneakyThrow(){
Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
}
复制代码
代码可以编译通过,这说明我们已经把 checked 异常转换成为 unchecked 异常了。
运行之后你可以得到下面的输出:
Exception in thread "main" java.io.IOException
at com.flydean.Main.lambda$sneakyThrow$1(Main.java:28)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.Streams$StreamBuilderImpl.forEachRemaining(Streams.java:411)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)
at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)
at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)
at com.flydean.Main.sneakyThrow(Main.java:28)
at com.flydean.Main.main(Main.java:9)
复制代码
从日志中,我们可以看出最后抛出的还是 java.io.IOException,但是如果我们尝试对这个异常进行捕获:
public static void sneakyThrow(){
try {
Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
}catch (IOException e){
System.out.println("get exception");
}
}
复制代码
在编译器中会提示编译不通过,因为代码并不会抛出 IOException。如果你把 IOException 修改为 RuntimeException,也没法捕获到最后的异常。
只能这样修改:
public static void sneakyThrow(){
try {
Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
}catch (Exception e){
System.out.println("get exception");
}
}
复制代码
才能最终捕获到 stream 中抛出的异常。所以如果你使用了我这里说的这种异常转换技巧,那就必须要特别注意这种异常的捕获情况。
对 lambda 的最终改造
上面可以封装异常了是不是就完成了我们的工作了呢?
并不是,因为我们在 map 中传入的是一个 Function 而不是一个专门的异常类。所以我们需要对 Function 进行额外的处理。
首先 JDK 中的 Function 中必须实现这样的方法:
如果这个方法里面抛出了 checked Exception,那么必须进行捕获,如果不想捕获的话,我们可以在方法申明中抛出异常,所以我们需要重新定义一个 Function,如下所示:
@FunctionalInterface
public interface FunctionWithThrow<T, R> {
R apply(T t) throws Exception;
}
复制代码
然后再定义一个 unchecked 方法,用来对 FunctionWithThrow 进行封装,通过捕获抛出的异常,再次调用 sneakyThrow 进行 checked 异常和 unchecked 异常的转换:
static <T, R> Function<T, R> unchecked(FunctionWithThrow<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (Exception ex) {
return SneakilyThrowException.sneakyThrow(ex);
}
};
}
复制代码
最后,我们就可以在代码中优雅的使用了:
public static void sneakyThrowFinal(){
try {
Stream.of(new MyStudents()).map(SneakilyThrowException.unchecked(MyStudents::changeAgeWithCheckedException)).toList();
}catch (Exception e){
System.out.println("get exception");
}
}
复制代码
总结
以上就是如何在 lambda 表达式中优雅的进行异常转换的例子了。大家使用的过程中一定要注意最后对异常的捕获。
好了,本文的代码:
本文的例子https://github.com/ddean2009/learn-java-base-9-to-20/tree/master/lambda-and-checked-exception/
更多文章请看 www.flydean.com
评论