为什么应该在参数中使用Java 8的Optional
我读过很多网站可选应该只用作返回types,而不是在方法参数中使用。 我正在努力寻找一个合乎逻辑的理由。 例如,我有一个有2个可选参数的逻辑。 因此,我认为这样写我的方法签名是有意义的(解决scheme1):
public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 { // my logic }
许多网页指定可选不应该被用作方法参数。 考虑到这一点,我可以使用下面的方法签名,并添加一个明确的Javadoc注释来指定参数可能为空,希望将来的维护人员将读取Javadoc,因此总是使用参数之前进行空检查(解决scheme2) :
public int calculateSomething(String p1, BigDecimal p2) { // my logic }
或者,我可以用四种公共方法replace我的方法,以提供更好的接口,并使其更明显p1和p2是可选的(解决scheme3):
public int calculateSomething() { calculateSomething(null, null); } public int calculateSomething(String p1) { calculateSomething(p1, null); } public int calculateSomething(BigDecimal p2) { calculateSomething(null, p2); } public int calculateSomething(String p1, BigDecimal p2) { // my logic }
现在我尝试编写为每种方法调用这段逻辑的类的代码。 我首先从另一个返回Optional
的对象中检索两个input参数,然后调用calculateSomething
。 因此,如果使用解决scheme1,调用代码将如下所示:
Optional<String> p1 = otherObject.getP1(); Optional<BigInteger> p2 = otherObject.getP2(); int result = myObject.calculateSomething(p1, p2);
如果使用解决scheme2,调用代码将如下所示:
Optional<String> p1 = otherObject.getP1(); Optional<BigInteger> p2 = otherObject.getP2(); int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));
如果解决scheme3被应用,我可以使用上面的代码,或者我可以使用以下(但它是更多的代码):
Optional<String> p1 = otherObject.getP1(); Optional<BigInteger> p2 = otherObject.getP2(); int result; if (p1.isPresent()) { if (p2.isPresent()) { result = myObject.calculateSomething(p1, p2); } else { result = myObject.calculateSomething(p1); } } else { if (p2.isPresent()) { result = myObject.calculateSomething(p2); } else { result = myObject.calculateSomething(); } }
所以我的问题是:为什么认为使用Optional
s作为方法参数是不好的做法(见解决scheme1)? 它看起来像对我来说是最可读的解决scheme,并使得最明显的是参数可能是未来的维护者空/空。 (我知道Optional
的devise者意味着它只能用作返回types,但是在这种情况下我找不到任何合理的理由)。
哦,这些编码风格是要采取一点盐。
- (+)将选项结果传递给另一个方法,而不进行任何语义分析; 把这个留给方法,是相当好的。
- ( – )使用在方法内部引起条件逻辑的可选参数实际上是对立的。
- ( – )需要在一个Optional中打包一个参数,对于编译器来说是次优的,并且做了不必要的包装。
- ( – )与可空参数相比,可选是更昂贵的。
一般来说:可选统一两个状态,必须解开。 因此,对于数据stream的复杂性来说,比input更适合结果。
我在这个主题上看到的最好的post是Daniel Olszewski写的,可以在http://dolszewski.com/java/java-8-optional-use-cases/find。; 而其他人提到,当你应该或不应该使用可选,这个职位实际上解释了为什么 。 在链接发生故障的情况下在这里交叉发布:
Java 8正式发布已有近两年的时间,许多关于新增强和相关最佳实践的优秀文章已经写入了当时。 令人惊讶的是,所有附加function中更有争议的话题之一是可选类 。 该types是一个容器,可以是空的或包含非空值。 这样的构造提醒了一个可选对象的用户,当内部什么都没有的情况下,必须适当地处理。 尽pipeJavadoctypes的定义是相当具有描述性的,但在识别有效的用例时,它却变得更加棘手。
方法结果
第一个可能的用例实际上是一个不容易的事情。 正在甲骨文公司从事Java语言工作的Brian Goetz 在Stack Overflow的回答中表示了这个目的,他认为这是 将该types添加到标准库中的主要动力 。
我们的目的是为库方法返回types提供一个有限的机制,在这个机制中需要有一个清晰的方法来表示“没有结果”,并且使用null来压倒性地导致错误。
在Java 8之前,从方法接收空值是不明确的。 这可能意味着没有什么可以返回,或者在执行过程中发生错误。 此外,开发人员往往忘记validation结果是否为空,导致在运行时产生讨厌的NullPointerException。 通过提供一种方便的方法来强制该方法的用户检查其自解释输出,可选地解决了这两个问题。
收集包装
在可选的方法结果中可选是可取的,当输出是集合或数组时,规则不适用。 在没有要返回的元素的情况下, 空实例优于空可选,因为它传递了所有必要的信息。 可选不提供任何额外的价值,只会使客户端代码复杂化,因此应该避免。 这是一个不好的练习样本:
Optional<List<Item>> itemsOptional = getItems(); if (itemsOptional.isPresent()) { // do we really need this? itemsOptional.get().forEach(item -> { // process item }); } else { // the result is empty }
只要
getItems()
方法返回解包的列表实例,客户端代码就可以摆脱一个条件检查,并简单地遍历集合。 如果我们想validation结果是否存在,则每个集合都有isEmpty()
方法,如果是数组,则可以使用length属性。 总体而言,可选添加了不必要的复杂性。构造函数和方法参数
尽pipe对于非强制性的方法参数可能会考虑使用Optional,但与其他可能的替代scheme相比,这样的解决scheme显得苍白无力。 为了说明问题,请检查以下构造函数声明:
public SystemMessage(String title, String content, Optional<Attachment> attachment) { // assigning field values }
乍一看,它可能看起来是一个正确的devise决定。 毕竟,我们明确地将附件参数标记为可选。 但是,至于调用构造函数,客户端代码可能会变得有点笨拙。
SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty()); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));
Optional类的工厂方法不是提供清晰度,而只是分散读者的注意力。 请注意,只有一个可选参数,但想象有两个或三个。 鲍勃叔叔肯定不会为这样的代码感到自豪
当一个方法可以接受可选参数时,最好采用成熟的方法,devise这种使用方法重载的方法。 在SystemMessage类的示例中,声明两个单独的构造函数优于使用Optional。
public SystemMessage(String title, String content) { this(title, content, null); } public SystemMessage(String title, String content, Attachment attachment) { // assigning field values }
这种改变使客户端代码更加简单易读。
SystemMessage withoutAttachment = new SystemMessage("title", "content"); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", attachment);
的POJO
POJO领域或获得者可能是最有争议的候选人使用和评论不同的博客post,文章和评论,我们都只能同意一件事:另一场圣战已经开始。 一方面,在上面的Stack Overflow文章中, Brian Goetz毫不怀疑这种types不适合访问者 。
我认为通常使用它作为获取者的返回值肯定会被过度使用。
更有甚者 , 可选地故意没有实现Serializable接口 ,这实际上将该types作为任何依赖于该机制的类的成员取消了资格。 对于许多开发者来说,这两个理由足以拒绝可选POJO字段的想法。
在硬币的另一面,其他人开始质疑前面提到的论点。 事实上,可空模型并不是罕见的情况,考虑可选是合理的。 Stephen Colebourne(大多数人称为Joda-Time的主要贡献者)和JSR-310规范在他的博客文章中提出要在一个类中保留可空字段,但是当它们通过公共getter离开私有范围时,将它们包含在Optional中 。 就像在方法结果的情况下,我们想知道其他开发人员可能缺乏价值。
除了内存开销以外, 阻止使用Optional作为POJO字段的主要障碍是对库和框架的支持 。 reflection广泛用于读取和操作对象,可选需要特殊处理。 例如,Jackson开发团队已经提供了一个额外的模块来处理可选字段,同时将POJO转换为JSON格式。 Hibernatevalidation器也可以与可选的实体字段一起工作,但是在很多情况下,你不能获得支持,另外一些额外的工作可能是不可避免的。
如果你select黑暗的一面而忽视Brian Goetz的build议,你必须确保你使用的所有库和框架都可以完全处理Optional类 。 无论你的团队决定什么时候开始一个新的应用程序,最好的build议是保持整个项目的一致性。
可选的类依赖
有时可以根据应用程序configuration来打开和closures业务逻辑的function或某些部分。 当这样的代码被外部化到一个单独的类中时,它成为一个可选的运行时依赖项。 无状态类不实现Serializable接口,因此使用Optional作为类字段没有技术障碍。 考虑下面的例子:
public class OrderProcessor { private Optional<SmsNotifier> smsNotifier = Optional.empty(); public void process(final Order order) { // some processing logic here smsNotifier.ifPresent(n -> n.sendConfirmation(order)); } public void setSmsNotifier(SmsNotifier smsNotifier) { this.smsNotifier = Optional.ofNullable(smsNotifier); } }
如果使用了一个可以为空的字段,那么在扩展类的时候,我们会冒险让某个人在不validation其存在的情况下在不知不觉中使用该字段。 在由dependency injection框架支配的世界中,许多开发人员会自动假设业务逻辑类中是否有一个字段,然后它必须由一个容器来设置 。 可选types在此情况下具有高度performance力,并防止发生自动行为。
像往常一样在编程,没有一个最好的办法来解决这个问题。 可选依赖关系的另一个可能的解决scheme是空对象模式 ,在某些情况下可能更好。
不是一颗silverlight的子弹
尽pipe在许多情况下使用Optional的诱惑力可能会很强,但是这种types背后的原始思想并不是为每个可空值创build一个通用的替代品。 在应用这个types之前,所有可能的select都应该被认为是过度使用可选的可能会导致引入新的繁重的代码气味 。 如果你有可选的实践经验,尤其是POJO领域或者getter输出,那么我很乐意阅读你学到的东西。 每一个评论都非常感谢,所以不要犹豫,分享你的意见。
没有使用可选参数几乎没有好的理由。 反对的论据依赖于权威的论据(参见Brian Goetz-他的论点是我们不能执行非null选项),或者可选参数可能为null(本质上是相同的论点)。 当然,Java中的任何引用都可以为空,我们需要鼓励编译器强制执行的规则,而不是程序员的内存(这是有问题的,不能扩展的)。
函数式编程语言鼓励可选参数。 使用这种方法的最好方法之一是有多个可选参数,并使用liftM2来使用函数,假定参数不为空,并返回一个可选参数(请参见http://www.functionaljava.org/javadoc/4.4/functionaljava/fj /data/Option.html#liftM2-fj.F- )。 Java 8不幸的是实现了一个非常有限的库支持可选。
作为Java程序员,我们应该只使用null来与遗留库进行交互。
带有Optional
的模式是为了避免返回 null
。 将null
传递给方法仍然是完全可能的。
尽pipe这些还不是真正的正式版本,但您可以使用JSR-308样式注释来指示是否接受null
值到函数中。 请注意,您必须拥有正确的工具才能真正识别它,并且它将提供比可执行的运行时策略更多的静态检查,但这将有所帮助。
public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}
这个build议是“对于投入尽可能不确定,并且尽可能具体地描述产出”的一个变体。
通常情况下,如果你有一个非空值的方法,你可以通过Optional
来映射它,所以plain版本对于input来说是非确定的。 但是,有一些可能的原因可能会导致你需要一个Optional
参数:
- 你希望你的函数和另一个返回一个
Optional
API结合使用 - 如果给定的值是空的,你的函数应该返回一个非空的可
Optional
-
你认为Optional
是如此之好,以至于无论使用你的API谁应该被要求了解它;-)
可选项并不是为此目的devise的, Brian Goetz很好地解释了这一点。
你总是可以使用@Nullable来表示一个方法参数可以为null。 使用可选方法并不能真正使您更方便地编写方法逻辑。
这对我来说似乎有点傻,但我能想到的唯一原因是方法参数中的对象参数已经是可选的 – 它们可以是null。 因此,迫使某人拿一个已经存在的对象并将其包装在一个可选项中是毫无意义的。
这就是说,把方法连接在一起,这是一个合理的事情,例如也许monad。
我认为这是因为你通常编写你的函数来操作数据,然后使用map
和类似的函数将它提升到可Optional
。 这将默认的Optional
行为添加到它。 当然,在有必要编写自己的Optional
辅助function时,可能会出现这种情况。
我相信存在的共鸣是你必须首先检查是否可选本身是空的,然后尝试评估它包装的价值。 太多的不必要的validation。
我认为,可选应该是一个Monad,这些在Java中是不可想象的。
在函数式编程中,您只处理纯粹的和更高阶的函数,它们只是基于它们的“业务域types”来构build它们的参数。 编写函数或者计算应该被报告给现实世界(所谓的副作用)的函数需要应用这些函数来自动地将值从代表外部世界的单元(状态,configuration,期货,也许,或者,作家等等); 这叫做解除。 你可以把它看作是一种分离的担忧。
混合这两个抽象层次不利于易读性,所以你最好避免它。
传递Optional
as参数时要小心的另一个原因是一个方法应该做一件事情…如果你传递一个Optional
参数,你可能会喜欢做多个事情,它可能类似于传递一个布尔参数。
public void method(Optional<MyClass> param) { if(param.isPresent()) { //do something } else { //do some other } }
还有一个办法,你可以做的是
// get your optionals first Optional<String> p1 = otherObject.getP1(); Optional<BigInteger> p2 = otherObject.getP2(); // bind values to a function Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}
一旦你build立了一个函数(在这种情况下是供应商),你将能够像任何其他variables一样传递它,并且能够使用它
calculatedValueSupplier.apply();
这里的想法是否有可选的价值将是你的function的内部细节,不会在参数。 在思考可选的参数时思考函数实际上是我find的非常有用的技巧。
至于你的问题,你是否应该真正做到这一点是根据你的喜好,但正如其他人所说,它使你的API丑陋至less可以说。
起初,我还希望传递Optionals作为参数,但是如果从API-Designer的angular度切换到API-用户的angular度,则会看到缺点。
对于你的例子,每个参数都是可选的,我build议把计算方法改成自己的类,如下所示:
Optional<String> p1 = otherObject.getP1(); Optional<BigInteger> p2 = otherObject.getP2(); MyCalculator mc = new MyCalculator(); p1.map(mc::setP1); p2.map(mc::setP2); int result = mc.calculate();
我知道这个问题更多是关于意见而不是硬性的事实。 但是我最近从一个.NET开发人员转到了Java开发人员,所以我最近才join了可选方。 另外,我更愿意说这是一个评论,但是由于我的观点水平不允许我发表评论,所以我不得不把这个作为答案。
我一直在做的,这是一个经验丰富的经验。 是使用可选的返回types,并且只使用可选参数作为参数,如果我需要可选的值和天气或没有可选的方法中有一个值。
如果我只关心值,在调用方法之前检查isPresent,如果在方法中有某种logging或不同的逻辑,取决于值是否存在,那么我会愉快地通过Optional。