Java Streams 中的异常处理
2021/7/31 11:07:08
本文主要是介绍Java Streams 中的异常处理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
这里写目录标题
- 处理异常代码块
- 运行时异常
- Either处理Stream异常
- 结束语
处理异常代码块
Stream API 和 lambda 是自第 8 版以来 Java 中的重大改进。从那时起,我们可以使用更具功能性的语法样式。现在,在使用这些代码结构几年后,仍然存在的较大问题之一是如何处理 lambda 中的已检查异常。
众所周知,不可能直接从 lambda 调用抛出已检查异常的方法。在某种程度上,我们需要捕获异常以使代码编译。自然,我们可以在 lambda 中执行一个简单的 try-catch 并将异常包装到 a 中 RuntimeException,如第一个示例所示,但这并不是最好的方法。
myList.stream() .map(item -> { try { return doSomething(item); } catch (MyException e) { throw new RuntimeException(e); } }) .forEach(System.out::println);
大多数人都知道这个 lambda块 很笨重且可读性较差。我们应该尽可能避免。如果我们需要做的不止一行,我们可以将函数体提取到一个单独的方法中,然后简单地调用新方法。解决这个问题的更好、更易读的方法是将调用包装在一个普通的方法中,该方法执行 try-catch 并从 lambda 中调用该方法。
myList.stream() .map(this::trySomething) .forEach(System.out::println); private Item trySomething(Item item) { try { return doSomething(item); } catch (MyException e) { throw new RuntimeException(e); } }
这个解决方案至少更具可读性,我们确实将我们的关注点分开了。如果您真的想捕获异常并做一些特定的事情,而不是简单地将异常包装到 RuntimeException中,这对您来说可能是一个可行的解决方案。
运行时异常
在许多情况下,您会看到人们使用这些类型的解决方案将异常重新打包成一个 RuntimeException或更具体的未检查异常的实现。通过这样做,可以在 lambda 内部调用该方法并在高阶函数中使用。
我可以将这种做法与这种做法联系起来,因为我个人认为一般来说检查异常没有太大价值,但这是另一个我不打算从这里开始的讨论。如果您想将每个调用都包装在一个 lambda 中,该 lambda 已检查为 RuntimeException,您将看到您重复相同的模式。为了避免一遍又一遍地重写相同的代码,为什么不把它抽象成一个实用函数呢?这样,您只需编写一次并在每次需要时调用它。
为此,您首先需要为函数编写自己的函数式接口版本
@FunctionalInterface public interface CheckedFunction<T,R> { R apply(T t) throws Exception; }
现在,您已准备好编写自己的通用实用程序函数,该函数接受CheckedFunction。您可以在此实用程序函数中处理 try-catch 并将原始异常包装到 RuntimeException或其他一些未经检查的异常中。
public static <T,R> Function<T,R> wrap(CheckedFunction<T,R> checkedFunction) { return t -> { try { return checkedFunction.apply(t); } catch (Exception e) { throw new RuntimeException(e); } }; } myList.stream() .map(wrap(item -> doSomething(item))) .forEach(System.out::println);
剩下的唯一问题是,当发生异常时,流的处理会立即停止。在许多情况下,直接终止并不理想。
Either处理Stream异常
在处理流时,如果发生异常,我们可能不想停止处理流。
让我们换个思路。为什么不像考虑“成功”结果一样尽可能多地考虑“特殊情况”。让我们将其视为数据,继续处理流,然后决定如何处理它。我们可以做到这一点,但需要引入一种新类型——Either 类型。
类似于 Java 中的 Optional 类型,Either是一个具有两种可能性的通用包装器。它可以是左或右,但不能同时是两者。left 和 right 都可以是任何类型。例如,如果我们有一个Either 值,则该值可以包含String 类型或Integer, 类型的内容Either<String,Integer>。
如果我们将这个原则用于异常处理,我们可以说我们的 Either 类型持有一个 Exception或一个值。为方便起见,通常左边是异常值,右边是成功值。
Either 类型的基本实现 :
public class Either<L, R> { private final L left; private final R right; private Either(L left, R right) { this.left = left; this.right = right; } public static <L,R> Either<L,R> Left( L value) { return new Either(value, null); } public static <L,R> Either<L,R> Right( R value) { return new Either(null, value); } public Optional<L> getLeft() { return Optional.ofNullable(left); } public Optional<R> getRight() { return Optional.ofNullable(right); } public boolean isLeft() { return left != null; } public boolean isRight() { return right != null; } public <T> Optional<T> mapLeft(Function<? super L, T> mapper) { if (isLeft()) { return Optional.of(mapper.apply(left)); } return Optional.empty(); } public <T> Optional<T> mapRight(Function<? super R, T> mapper) { if (isRight()) { return Optional.of(mapper.apply(right)); } return Optional.empty(); } public String toString() { if (isLeft()) { return "Left(" + left +")"; } return "Right(" + right +")"; } }
你现在可以让自己的函数返回一个 Either 而不是抛出一个 Exception。但是,如果你想使用Exception 在 lambda中抛出异常,这对个方法还需要些改进,因此,我们必须为Either 做点改动 。
public static <T,R> Function<T, Either> lift(CheckedFunction<T,R> function) { return t -> { try { return Either.Right(function.apply(t)); } catch (Exception ex) { return Either.Left(ex); } }; }
通过将这个静态方法添加到 Either,我们现在可以简单地抛出已检查异常的函数并让它返回一个 Either。最终会得到一个 Stream ,而不是一个可能 RuntimeException 就会炸毁我整个 Stream。
这只是意味着我们已经收回了控制权。通过使用 Stream 中的过滤器功能,我们可以简单地过滤掉左边的实例,然后记录它们或者做其他的处理。你还可以过滤正确的实例并简单地忽略其他情况。无论哪种方式,您都将重新获得控制权,并且您的流不会因RuntimeException发就立即终止 。
因为 Either 是通用包装器,所以它可以用于任何类型,而不仅仅是用于异常处理。这让我们有机会做更多的事情,而不仅仅是将Exception包装到 Either。我们现在可能遇到的问题是,如果 Either 仅仅持有包装的异常,我们无法重试,因为我们丢失了原始值。通过使用Either 保存任何东西的能力 ,我们可以将异常和值都存储在左值中。为此,我们需要给Either来点小小的增强。
public static <T,R> Function<T, Either> liftWithValue(CheckedFunction<T,R> function) { return t -> { try { return Either.Right(function.apply(t)); } catch (Exception ex) { return Either.Left(Pair.of(ex,t)); } }; }
在此 liftWithValue 函数中, Pair 类型用于将异常和原始值配对到Either,现在左侧如果出现问题,我们将拥有需要的所有信息,而不是只有 Exception。
Pair 这里使用的 类型是另一种可以在 Apache Commons lang 库中找到的泛型类型,或者您可以简单地实现自己的类型。无论如何,它只是一种可以容纳两个值的类型。
public class Pair<F,S> { public final F fst; public final S snd; private Pair(F fst, S snd) { this.fst = fst; this.snd = snd; } public static <F,S> Pair<F,S> of(F fst, S snd) { return new Pair<>(fst,snd); } }
通过使用 liftWithValue,您现在可以灵活地控制使用可能Exception 在 lambda 内部抛出 的方法 。当 Either 是right的时候,我们知道函数被正确应用,我们可以提取结果。另一方面,如果 Either 是left的时候,我们就知道出了点问题,我们可以同时提取Exception值和原始值,因此我们可以随心所欲地进行。通过使用 Either 类型而不是将已检查的包装 Exception 到 RuntimeException中,我们可以防止 Stream 中途终止。
结束语
当你想使用一个抛出异常的方法时, 如果你想在 lambda 中调用它checkedException,你必须做一些额外的事情。将它包装成 一个RuntimeException 一种解决方案。如果您更喜欢使用这种方法,我强烈建议您创建一个简单的包装器工具并重复使用它,这样您就不会每次try/catch。
如果你想拥有更多的控制权,你可以使用 Either类型来包装函数的结果,这样你就可以将它作为一条数据来处理。抛出RuntimeException时流不会终止,您可以随意处理流中的数据
这篇关于Java Streams 中的异常处理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-09-28AI给的和自己写的Python代码,都无法改变输入框的内容,替换也不行
- 2024-09-27Sentinel配置限流资料:新手入门教程
- 2024-09-27Sentinel配置限流资料详解
- 2024-09-27Sentinel限流资料:新手入门教程
- 2024-09-26Sentinel限流资料入门详解
- 2024-09-26Springboot框架资料:初学者入门教程
- 2024-09-26Springboot框架资料详解:新手入门教程
- 2024-09-26Springboot企业级开发资料:新手入门指南
- 2024-09-26SpringBoot企业级开发资料新手指南
- 2024-09-26Springboot微服务资料入门教程