优雅地在Java 8中处理异常的方法详解

前言

Java 8 引入的流 (Stream) API 和 lambda 表达式为我们打开了新世界的大门,自此之后我们也可以在 Java 中进行函数式编程了。然而,在实际工作中,许多小伙伴并不知道如何正确的在 lambda 中处理异常,今天就来给大家讲解一下。

小编给大家推荐一个Java技术交流群:937053620!群内提供设计模式、spring/mybatis源码分析、高并发与分布式、微服务、性能优化,面试题整合文档等免费资料!给大家提供一个交流学习的平台!

我们都知道,Java 异常分为检查异常和非检查异常。检查异常就是编译器要求开发者必须处理的异常,而非检查异常则没有这个要求。所以当我们需要调用某个抛出检查异常的方法时,必须明确捕获它:

myList.stream()
 .map(item ->
  try{
  return doSomething(item);
  } catch(MyException e){
  throw new RuntimeException (e);
  }
 })
 .forEach(System.out::printion);

如上面代码所示,我们捕获了 MyException 这个检查异常,然后将其转化为 RuntimeException 非检查异常,重新抛出。但是你自己心里面其实清楚的很,这不是最好的处理方式。

优化一: 提升可读性

如下所示,我们将方法体单独提取到 trySomething 方法中,这样的话,我们就可以使用一行代码完成 lambda 表达式,整个代码可读性也会提升不少:

myList.stream()
 .map(this::trySomething)
 .forEach(System.out::printion);

private Item trySomething(Item item) {
 try{
  return doSomething(item);
 } catch(MyException e){
  throw new RuntimeException (e);
 }
}

优化二: 复用代码

现在你已经解决了上述的问题,然而当我们再碰到需要处理异常的其它方法时,难道我们都要用 try ... catch ... 包裹一层吗?那样的话,你可以想象代码中可能到处都是这种类似的写法。为了避免陷入到这种重复的写法中,我们应该将上述代码片段抽象为一个小的工具类,专门用来干这件事情。你只需要定义一次,然后再需要的地方多次调用它就可以了。

为了实现这个目标,我们首先需要自己定义一个函数式接口,这个接口可能会抛出一个异常:

然后,我们来写一个静态帮助函数 wrap ,该方法接受一个函数式接口参数,在方法体内捕获检查异常,并抛出非检查异常 RuntimeException:

借助于 wrap 静态函数,现在你可以在 lambda 表达式中这么写了

优化三: 出现异常时继续运行

上述代码的可读性、抽象性已经很好了,然而还存在一个比较大的问题,那就是当出现异常的时候,你的 stream 代码会立即停止,不会接着处理下一个元素。大多数情况下,当抛出异常的时候,我们可能还想让 stream 继续运行下去。

我们与其抛出异常,将异常当成一种特殊的情况处理,还不如直接将异常当成是一个 “正常” 的返回值。即这个函数要么返回一个正确的结果,要么返回一个异常,所以我们现在需要定义一个新的封装类 Either,用来存储这两种结果。为了方便,我们将异常存储到 left 这个字段中,将正常返回的值存储到 right 这个字段中。下面就是 Either 类的一个简单示例:

public class Eithercl<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 I- 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 Optionalempty();
}
public String tostring(){
 if (isleft()){
  return"Left(”+left+")";
 }
 return "Right("+ right +")";
 }
}

现在我们需要再定义一个 lift 函数,该函数内部将 function 函数正常返回的值或者抛出的异常都使用 Either 类进行了一层封装

现在我们的代码变成这个样子了,也不用担心方法抛出异常会提前终止 Stream 了

优化四: 保留原始值

现在思考一个问题,如果在上述处理过程中,当结果是异常信息的时候,我们想要重试,即重新调用这个方法怎么办? 你会发现我们 Either 封装类没有保存最原始的这个值,我们丢掉了原始值,因此我们可以进一步优化,将原始值 t 也封装进 left 字段中,就像下面这样:

Pair 类是一个非常简单的封装类,用以封装两个值:

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);
 }
}

这样,当我们遇见异常的时候,我们可以从 Pair 中取出最原始的值 t,无论是想重试,还是做一些其他操作,都很方便了。

总结

我们经过上文一点一点地优化代码,得到了一个比较满意的在 Java 8 中处理异常的通用方式。其实,大家还可以关注 Github 上的有关函数式编程方面的库,比如 Javaslang ,它实现了多种多样的函数式帮助方法和封装类来帮助开发者写好 lambda 表达式。但是,如果你只是为了处理异常,而引入这么大的一个第三方库的话,就不太建议了哦~

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Java异常处理之try...catch...finally详解

    异常处理机制已经成为判断一门编程语言是否成熟的标准之一,其对代码的健壮性有很大影响.一直以来异常处理使用不是很得心应手,今天对异常进行了较为深入的学习,这篇主要是对try-catch-finally的一个总结. 一.java继承体系 Java语言为异常处理提供了丰富的异常类,这些类之间有严格的继承关系.如图: 从图中我们可以看出,所有的类都是继承于Throwable这个父类,java将所有的非正常情况分为两种:Error(错误)和Exception(异常),Error错误一般是于虚拟机相关的问题

  • Java异常处理与throws关键字用法分析

    本文实例讲述了Java异常处理与throws关键字用法.分享给大家供大家参考,具体如下: Java异常处理 认识异常: 1.异常是导致程序中断运行的一种指令流,如果不对异常进行正确处理,则可能导致程序的中断执行,造成不必要的损失. 2.异常范例 空指针异常 Exc e=null; System.out.println(e.i); 除0异常 int a=10; int b=0; System.out.println(a/b); 3.处理异常 异常格式: try{ 异常语句: } catch(Exc

  • Java的异常类型总结

    Java的设计目的是让程序员有机会设计一个没有错误的应用程序.当应用程序与资源或用户交互时,程序员可能会知道一些异常,这些异常是可以处理的.不幸的是,也有程序员无法控制或简单忽略的例外情况.简而言之,并不是所有的异常都是相同的,因此程序员需要考虑几种类型. 异常是导致程序无法在其预期的执行中运行的事件.异常有三种类型--检查异常.错误和运行时异常. The Checked Exception(检查异常) 已检查异常是Java应用程序应该能够处理的异常.例如,如果应用程序从文件中读取数据,它应该能

  • Java异常处理中的各种细节汇总

    前言 今天我们来讨论一下,程序中的错误处理. 在任何一个稳定的程序中,都会有大量的代码在处理错误,有一些业务错误,我们可以通过主动检查判断来规避,可对于一些不能主动判断的错误,例如 RuntimeException,我们就需要使用 try-catch-finally 语句了. 有人说,错误处理并不难啊,try-catch-finally 一把梭,try 放功能代码,在 catch 中捕获异常.处理异常,finally 中写那些无论是否发生异常,都要执行的代码,这很简单啊. 处理错误的代码,确实并

  • 优雅地在Java 8中处理异常的方法详解

    前言 Java 8 引入的流 (Stream) API 和 lambda 表达式为我们打开了新世界的大门,自此之后我们也可以在 Java 中进行函数式编程了.然而,在实际工作中,许多小伙伴并不知道如何正确的在 lambda 中处理异常,今天就来给大家讲解一下. 小编给大家推荐一个Java技术交流群:937053620!群内提供设计模式.spring/mybatis源码分析.高并发与分布式.微服务.性能优化,面试题整合文档等免费资料!给大家提供一个交流学习的平台! 我们都知道,Java 异常分为检

  • 详解Java中NullPointerException异常的原因详解以及解决方法

    NullPointerException是当您尝试使用指向内存中空位置的引用(null)时发生的异常,就好像它引用了一个对象一样. 当我们声明引用变量(即对象)时,实际上是在创建指向对象的指针.考虑以下代码,您可以在其中声明基本类型的整型变量x: int x; x = 10; 在此示例中,变量x是一个整型变量,Java将为您初始化为0.当您在第二行中将其分配给10时,值10将被写入x指向的内存中. 但是,当您尝试声明引用类型时会发生不同的事情.请使用以下代码: Integer num; num

  • java异常处理throws完成异常抛出详解

    已检查异常抛出 对于已检查异常(checked exceptions),编译器强制要求捕获并处理可能发生的异常,不处理就不能通过编译.但调用的方法没有能力处理这种异常,对于这种情况,可以在方法声明处使用throws子句来声明抛出异常,而是调用层次向上传递,谁调用这个方法,这个异常就由谁来处理.如:在service层读取文件,如果文件不存在,则需要将文件不存在的这条信息反馈给用户.要求在service层将此异常向上抛,用户层调用services层获取此条信息,反馈给用户.示例代码如下: 创建类Re

  • JAVA基于Slack实现异常日志报警详解

    目录 一.功能介绍 二.Slack介绍 三.前期准备 slack配置 pom.xml 四.具体实现 1.实现Slack发送消息 SlackUtil 给Slack发消息工具类 向 webhook发起请求通过Urlencode SlackUtil测试 2.重写打印日志类 常见异常打日志处理 重写封装打印日志的方法 测试日志类 五.优化扩展想法 其他代码 一.功能介绍 在我们日常开发中,如果系统在线上环境上,发生异常,开发人员不能及时知晓来修复,可能会造成重大的损失,因此后端服务中加入异常报警的功能是

  • java 中enum的使用方法详解

    java 中enum的使用方法详解 enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性,存放在 java.lang 包中. 下面是我在使用 enum 过程中的一些经验和总结. 原始的接口定义常量 public interface IConstants { String MON = "Mon"; String TUE = "Tue"; String WED = "Wed"; String THU = "Thu

  • Java编程中的HashSet和BitSet详解

    Java编程中的HashSet和BitSet详解 我在Apache的开发邮件列表中发现一件很有趣的事,Apache Commons包的ArrayUtils类的removeElements方法,原先使用的HashSet现在换成了BitSet. HashSet<Integer> toRemove = new HashSet<Integer>(); for (Map.Entry<Character, MutableInt> e : occurrences.entrySet()

  • java 中迭代器的使用方法详解

    java 中迭代器的使用方法详解 前言: 迭代器模式将一个集合给封装起来,主要是为用户提供了一种遍历其内部元素的方式.迭代器模式有两个优点:①提供给用户一个遍历的方式,而没有暴露其内部实现细节:②把元素之间游走的责任交给迭代器,而不是聚合对象,实现了用户与聚合对象之间的解耦. 迭代器模式主要是通过Iterator接口来管理一个聚合对象的,而用户使用的时候只需要拿到一个Iterator类型的对象即可完成对该聚合对象的遍历.这里的聚合对象一般是指ArrayList,LinkedList和底层实现为数

  • Java语言中的内存泄露代码详解

    Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存.理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同. JAVA中的内存管理 要了解Java中的内存泄露,首先就得知道Java中的内存是如何管理的. 在Java程序中,我们通常使用new为对象分配内存,而这些内存空间都在堆(Heap)上. 下面看一个示例: public class Simple { public static vo

  • java web中对json的使用详解

    一.在Java Web的开发过程中,如果希望调用Java对象转化成JSON对象等操作.则需要引入以下jar包,不然运行时则报错. 1.commons-beanutils.jar 2.commons-collections.jar 3.commons-lang.jar 4.commons-logging-1.1.jar 5.ezmorph-1.0.3.jar 6.json-lib-2.0-jdk15.jar 7.有人说还需要 commons-httpclient.jar 引入成功之后,使用JSON

  • Java开发中为什么要使用单例模式详解

    一.什么是单例模式? 单例设计模式(Singleton Design Pattern)理解起来非常简单.一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式. 二.实战案例一:处理资源访问冲突 我们先来看第一个例子.在这个例子中,我们自定义实现了一个往文件中打印日志的 Logger 类.具体的代码实现如下所示: public class Logger { private FileWriter writer; public Logger() {

随机推荐