Java Optionals获得更具表现力的代码

我们中任何人使用允许空引用的语言进行编程时,都会遇到尝试取消引用一个引用时发生的情况。 无论是导致segfault还是NullPointerException,它始终是一个错误。 托尼·霍尔将其描述为他十亿美元的错误 。 当函数向客户端的开发人员未预料到的客户端返回空引用时,通常会发生此问题。 用这样的代码说:

User user = userRepository.find("Alice");

一个精明的程序员会立即询问没有找到匹配“ Alice”的用户,但是find()方法的签名中没有任何内容告诉您期望什么。 过去,典型的Java解决方案是使该方法引发一个已检查的异常,也许是UserNotFoundException 。 这肯定会与客户程序员交流这种可能性的发生,但是它并不能提高他们代码的表达力。 捕获异常会导致代码妨碍理解。 无论如何,检查异常都不受欢迎,人们不再倾向于编写引发异常的代码。

许多程序员将改为抛出未检查的异常或返回空引用。 两者彼此一样坏,并且出于相同的原因:它们都没有通知程序员期望这种可能性,并且如果处理不当,它们都将导致运行时失败。 Java 8引入了Optional类型来处理此问题。 每当编写可能返回值或可能不返回值的方法时,都应使该方法返回希望返回的任何类型的Optional。 因此,在上面的示例中,find将返回Optional<User>类型的值。 客户端代码现在需要执行其他步骤来测试的存在,然后获取值:

Optional<User> userOpt = userRepository.find("Alice");
if (userOpt.isPresent()) {User user = userOpt.get();  
}

而且,如果代码不加保护地调用get()则其IDE可能会警告他们。

Lambdas使事情变得更好

该解决方案已经好很多了,但是Optional不仅仅在于:如果您坚持以这种方式处理可选内容,那么您将失去一些使代码更具表现力的机会。

上面的代码段改编自我自己对Codurance用于测试求职者的“社交网络”练习的实现。 我的实际代码更像是:

Optional<User> userOpt = userRepository.find(subject);
if (userOpt.isPresent()) {User user = userOpt.get();printAllMessagesPostedToUser(user);
}

Optional具有ifPresent()方法,该方法允许我们提供一个Consumer ,如果存在Optional,则将调用该Consumer 。 消费者的参数将是由可选包装的对象。 这使我们可以像这样重写代码:

userRepository.find(subject).ifPresent(user -> printAllMessagesPostedToUser(user));

实际上,我们可以更进一步,并用方法引用代替lambda:

userRepository.find(subject).ifPresent(this::printAllMessagesPostedToUser);

我认为这比if语句更清楚地传达了程序员的意图(在本例中为我的意图)。

令人疯狂的是,没有ifNotPresent()对应项,即使有,也没有ifPresent是一个void方法,因此它们无论如何都无法链接。 Java 9通过其ifPresentOrElse(Consumer<T>, Runnable)方法来解决此问题,但是它仍然不是理想的选择。

替换默认值

关于不存在可选值的问题,我们该怎么办? 如果忘记了缺少功能的抱怨, ifPresent()仅适用于具有副作用的命令。 如果要实现查询,则可能需要用默认值替换为空的可选值,例如:

if (optionalValue.isPresent()) {return optionalValue.get();
}
return defaultValue;

使用Optional.orElse()可以很容易地做到这一点:

return optionalValue.orElse(defaultValue);

当您必须调用可能返回null且不受您控制的方法时,这也提供了一种方便的方法,可以将值清零。 之前,我们都有所有与此类似的书面代码:

value = methodThatMayReturnNull();
if (value == null) {value = defaultValue;
}

您可以使用Optional.ofNullable()重构该代码,因为如果该值为null,它将返回Optional.empty()

value = Optional.ofNullable(methodThatMayReturnNull()).orElse(defaultValue);

我认为这比使用ObjectUtils.defaultIfNull做同样的事情要好一些。 但是,有一个警告。 您不得使用Optional.orElse()来调用具有副作用的方法。 例如,在我的社交网络练习的其他地方,我有搜索用户的代码,并在找到用户时将其返回,否则它将创建一个新用户:

Optional<User> userOpt = userRepository.find(recipient);
if (userOpt.isPresent()) {return userOpt.get();
}
return createUser();

您可能会假设您可以像下面这样重写代码:

return userRepository.find(recipient).orElse(createUser());

您不必这样做,因为无论是否存在可选参数,都会始终调用createUser() ! 几乎可以肯定这不是您想要的:充其量您将进行不必要的方法调用,并且,如果该方法有副作用,则可能会引入错误。 相反,您应该调用Optional.orElseGet()并为它提供一个提供默认值的Supplier

return userRepository.find(recipient).orElseGet(() -> createUser());

现在,仅当不存在可选参数时才调用createUser() ,这是我想要的行为。 再一次,我们可以将lambda替换为方法参考:

return userRepository.find(recipient).orElseGet(this::createUser);

抛出异常

对于您来说,当可选项不存在并且您想引发异常时,可能是错误情况。 您可以通过调用Optional.orElseThrow()并将其传递给创建异常的Supplier来实现:

return userRepository.find(recipient).orElseThrow(() -> new RuntimeException("User " + recipient + " not found"));

映射可选值

Optional还具有一些方法,使您可以执行类似于流上的操作。 例如,在另一个练习中,我有一些结构类似于此的代码:

Optional<Amount> creditAmountOpt = transaction.getCreditAmount();
Optional<Amount> debitAmountOpt = transaction.getDebitAmount();String formattedDepositAmount = creditAmountOpt.isPresent() ?formatAmount(creditAmountOpt.get()) : " ";String formattedWithdrawalAmount = debitAmountOpt.isPresent() ?formatAmount(debitAmountOpt.get()) : " ";return String.format(" %s| %s|", formattedDepositAmount, formattedWithdrawalAmount);

该代码的上下文是一个打印银行对帐单行的类:我的Transaction类知道它是存款还是提款,但我不希望对帐单行打印机知道。 因此,我让Transaction接口返回了借方和贷方金额的可选值:如果行存在,对帐单行打印机将格式化每个值,否则将替换为空白。

为了避免条件运算符,我们可以使用Optional.map()方法。 这与Stream API上的map方法非常相似。 它接受一个Function并在存在可选选项时调用它。 它将包装的值作为函数参数传递,并将返回值包装在另一个Optional中。 因此,在这种情况下,它将Optional<Amount>映射到Optional<String> 。 这使我们可以像这样重写代码:

return String.format(" %s| %s|",transaction.getDepositAmount().map(this::formatAmount).orElse(" "),transaction.getWithdrawalAmount().map(this::formatAmount).orElse(" "));

您可能想知道,如果映射一个返回另一个可选参数的函数,即Function<T, Optional<U>> ,在这种情况下,您最终得到的是Optional<Optional<U>>类型的结果,可能不是你要。 同样,类似于流,您可以改用flatMap()来返回Optional<U>值。

与流的相似性扩展到Optional.filter() ,如果存在可选值,则评估提供的谓词,当谓词评估为false时,它将返回一个空的可选值。 明智的是,避免变得过于可爱,如果不加小心,您可能会得到难以理解的代码。 可选参数最适合用于重构简单明了的代码,但将其长期保存为简单明了的代码。

但小心点

最后,任何有用的工具都可能被滥用,因此Optional也是如此。 它们仅用于表示返回值。 如果您声明类型为Optional的实例变量,则IntelliJ会警告您。 这构成了一个临时字段的显式声明,该字段被视为代码气味 。 另外,不要将Optionals用作方法参数:本质上,这是伪装的布尔型参数,也被认为很臭。 如果发现自己想要这样做,最好将方法分为两个方法:一个带有参数,另一个不带参数,然后将条件放在客户端代码中。

翻译自: https://www.javacodegeeks.com/2017/11/java-optionals-expressive-code.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/348816.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Atom使用方法(快捷键,插件,汉化)

Atom文本编辑软件 使用方法 常用快捷键 Ctrl Shift M &#xff1a;打开markdown调试窗口 Crtl m&#xff1a;相应括号之间&#xff0c;html tag之间等跳转 Crtl Alt B&#xff1a; 格式化代码&#xff08;需要安装atom-beautify&#xff09; Crtl &#xff1a;调起CLI…

将测微仪与Spring Boot 2一起使用

这是快速入门&#xff0c;介绍了如何使用出色的Micrometer库来检测基于Spring Boot 2的应用程序并在Prometheus中记录指标 介绍 Micrometer在各种监视工具提供的客户端库上提供了基于Java的外观。 以Prometheus为例&#xff0c;如果我要将Java应用程序与Prometheus集成&#…

学习笔记整理

毕设学习笔记整理说明Python和Pycharm使用方面因网络问题导致pycharm安装第三方库失败的解决办法将python程序打包为exe程序pyinstallerpy2exepycharm取消缩进Python函数方面python中 if __name__ __main__: 的作用与意义cv2.resize的用法只读取图像的单通道数值最值索引图像增…

与Maven的集成测试

用Maven实施单元测试是很普通的事情&#xff0c;我们大多数人都熟悉项目结构以及单元测试所在的位置。 但是&#xff0c;集成测试是一种不同的情况&#xff0c;大多数情况下它们具有完全不同的要求。 例如&#xff0c;可以让您的单元测试在内存数据库中的h2上运行&#xff0c;…

通信原理-通信系统的组成

第一章 通信系统的组成 1、通信系统一般模型 发送设备&#xff1a;将信源产生的原始电信号变换成适合在信道中传输的形式。变换方式有调制、放大、滤波、编码、多路复用等。 信道&#xff1a;传输信号的通道.即传输媒质。在给子信号通道的同时&#xff0c;信道也会对信号产生损…

使用log4j2免费分配日志记录

介绍 最近&#xff0c;我正在为一个客户端工作&#xff0c;试图为大型精心制作的Java系统消除一些GC暂停。 经过分析后&#xff0c;我意识到大部分垃圾都是通过日志记录产生的&#xff01; 是否有一种简单的方法来删除所有分配&#xff1f; 原来有:) 我应该使用哪个框架进行GC…

Verilog中fork...join 的用法

特点 中间的语句并行执行&#xff1b;&#xff08;延时不累加&#xff09; 不能用于综合&#xff1b; 代码 module signal_gen; reg wave; parameter cycle 5; initial beginforkwave 0;#(cycle) wave 1;#(2*cycle) wave 0;#(3*cycle) wave 1;#(4*cycle…

使用JWT的Cloud Native应用程序

本机云应用程序是为云计算环境开发的应用程序。 对于“ 什么是云原生应用程序 ”这个问题没有具体答案&#xff0c;但是必须满足不同的概念。 在我看来&#xff0c;最重要的功能之一就是能够快速缩放 。 这意味着我们的应用程序在每台服务器上都无法具有任何状态&#xff0c;…

stackexchange_通过Spring Social发推StackExchange问​​题

stackexchange1.简介 这是有关小型辅助项目的第三篇也是最后一篇文章-该机器人自动在专用帐户上发布来自各个Q&#xff06;A StackExchange网站上的问题的推文&#xff08;文章末尾的完整列表&#xff09;。 第一篇文章讨论了为StackExchange REST API构建简单的客户端 。 在第…

使用JCache缓存方法结果

在JCache中&#xff0c;有一个方便的功能可以透明地缓存方法的结果。 您可以使用CacheResult注释托管bean的方法&#xff0c;并且将再次返回第一次调用的结果&#xff0c;而无需再次调用实际方法。 import javax.cache.annotation.CacheResult; // ...public class Calculator…

开源项目GoodView点赞效果

点赞1效果&#xff1a; GoodView方法&#xff1a; 使用GoodView的Demo: public class MainActivity extends Activity { Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main)final Good…

开源RefreshListView下拉刷新效果

1、AnimationDrawable java.lang.Object ↳android.graphics.drawable.Drawable ↳android.graphics.drawable.DrawableContainer ↳android.graphics.drawable.AnimationDrawable 文档概述&#xff1a;An object used to create frame-by-frame animations, defined …

Neo4j:遍历查询超时

在过去的几周中&#xff0c;我一直在花一些业余时间来创建一个应用程序&#xff0c;该应用程序从Open Roads数据生成运行路线-当然已转换并导入到Neo4j中&#xff01; 我创建了一个用户定义的过程&#xff0c;该过程结合了几个最短路径查询&#xff0c;但是如果它们花费的时间…

View的三大流程之View的测量

1、public class View extends Objectimplements Drawable.Callback KeyEvent.Callback AccessibilityEventSourcejava.lang.Object ↳android.view.View Class Overview This class represents the basic building block for user interface components. A View occupies a …

使用LayoutParams设置布局

1、public static class ViewGroup.LayoutParams extends Objectjava.lang.Object ↳android.view.ViewGroup.LayoutParams Class Overview LayoutParams are used by views to tell their parents how they want to be laid out. LayoutParams是ViewGroup的一个内部类, 每个…

记录意外的开关选项

Java开发人员可以做很多事情来使自己的生活以及维护该代码的其他人的生活更加轻松。 在本文中&#xff0c;我将探讨开发人员可以采用的一种非常简单的方法&#xff0c;以使每个人都更轻松。 这篇文章的要点对于每个阅读它的人来说似乎都是显而易见的&#xff0c;但是我发现这样…

View的绘制

1、当测量好一个View后就可以重写onDraw()方法&#xff0c;并在Canvas对象上绘制所需的图形。 public class Canvas extends Objectjava.lang.Object ↳android.graphics.Canvas Public ConstructorsCanvas()Construct an empty raster canvas.Canvas(Bitmap bitmap)Construc…

ViewGroup的测量及绘制

1、ViewGroup的测量public abstract class ViewGroup extends Viewimplements ViewManager ViewParent java.lang.Object ↳android.view.View ↳android.view.ViewGroup Class Overview A ViewGroup is a special view that can contain other views (called children.)…

ejb 2.0 3.0_定义EJB 3.1视图(本地,远程,无接口)

ejb 2.0 3.0这篇文章将讨论使用批注定义EJB视图的可能方法&#xff08;最后我将只提到使用EJB部署描述符&#xff09;。我将重点介绍最新的EJB 3.1视图&#xff0c;这些视图将省略旧的本地&#xff0c;远程和本地接口。 因此&#xff0c;我们可以选择&#xff1a; 远程业务界面…

View的事件分发机制简述

要分析的对象就是MotionEvent&#xff0c;点击事件的事件分发其实就是对MotionEvent事件的分发过程&#xff0c;当MotionEvent产生后&#xff0c;系统需要把这个事件传递给一个具体的View&#xff0c;这个传递过程就是分发过程。这个过程由三个很重要的方法共同完成&#xff1a…