可选vs抛出异常

Dmy*_*tro 19 java optional java-8

是不是因为Java 1.8返回Optional对象比抛出异常更可取?我越来越多地看到这样的代码:

  public Optional<?> get(int i) {
        // do somtething
        Object result = ...
        Optional.ofNullable(result);
    }
Run Code Online (Sandbox Code Playgroud)

而不是这个:

public Object get(int i) {
        if(i<0 || i>=size) {
            throw new IndexOutOfBoundsException("Index: " + i + ". Size: " + size);
        }
        // do somtething
        Object result = ...
        return result;
    }
Run Code Online (Sandbox Code Playgroud)

是否意味着我们需要忘记旧方法并使用新方法?什么地方Optional适合?

Mar*_*nik 36

您提供的示例不是 Optional的适当用法.空Optional表示由于调用者无法预测的原因而缺少的值.它是法律调用方法的结果.

您作为"旧习惯用法"呈现的代码会执行输入验证,如果输入无效,则会抛出未经检查的异常.即使您引入Optional,此行为也应保持不变.唯一的区别是返回值Object get(i)可能为null,而返回值Optional<?> get(i)永远不为null,因为Optional实例的特殊状态表示缺少值.

返回Optional而不是可空值的方法的优点是消除了样板代码,该代码必须在尝试对返回值执行任何操作之前进行例行的空检查.纯粹在方法中使用Optional有许多其他优点.例如:

static Optional<Type> componentType(Type type) {
  return Optional.of(type)
                 .filter(t -> t instanceof ParameterizedType)
                 .map(t -> (ParameterizedType) t)
                 .filter(t -> t.getActualTypeArguments().length == 1)
                 .filter(t -> Optional.of(t.getRawType())
                                      .filter(rt -> rt instanceof Class)
                                      .map(rt -> (Class<?>) rt)
                                      .filter(Stream.class::isAssignableFrom)
                                      .isPresent())
                 .map(t -> t.getActualTypeArguments()[0]);
Run Code Online (Sandbox Code Playgroud)

这里有一个重要的好处是完美的范围控制:t在每个新范围内,对于适合于该处理阶段的类型的变量,重用相同的名称.因此,不是在其使用寿命到期后被迫在范围内使用变量,而是为每个后续变量创建一个新名称,使用这个习惯用法,我们有了我们需要进行的精确最小值.

只是为了感兴趣,您可以equals完全按照Optional执行:

@Override public boolean equals(Object obj) {
  return Optional.ofNullable(obj)
                 .filter(that -> that instanceof Test)
                 .map(that -> (Test)that)
                 .filter(that -> Objects.equals(this.s1, that.s1))
                 .filter(that -> Objects.equals(this.s2, that.s2))
                 .isPresent();
}
Run Code Online (Sandbox Code Playgroud)

虽然我发现这个成语非常干净和可读,但它目前还没有足够优化,不适合作为一个有价值的选择推荐.但是,Java的未来版本可能会使这一点变得可行.

  • 虽然我同意你的推理,但我想知道你在这里呈现的代码对我来说可能需要多少练习,因为非功能性代码本来就是这样.坦率地说,在这种情况下,我认为老式代码在可读性方面更胜一筹.但话说回来,我的生活中从来没有做过多少Haskell. (3认同)
  • 我从未写过一系列Haskell或任何类似的语言,但在真诚使用它们的几周内,我选择的成语变得很自然.当然,这是个人的,但我的预测是大多数人不需要超过一两个月的时间来适应它并开始获得收益. (3认同)
  • 是的,你必须调整你对正在发生的事情的心理描述.它的美妙之处在于,如果没有Optional,你必须用"if"的另一个嵌套级别或者早期返回来替换每个`filter`.对于上面代码中的inner可选,如果你想使用早期返回习语,你可能需要一个单独的方法.与[此代码](http://bit.ly/18BpAnV)比较,这是我的起点.需要花费大量的精力和时间来跟踪每个标识符名称的声明,并跟踪每个名称的使用位置. (3认同)

Bri*_*etz 26

可以平等地滥用异常,空值和可选项.在这种特殊情况下,我认为你可能会滥用可选项,因为你默默地隐藏了一个前提条件违规并将其转换为正常返回.在收到代码中的空选项后,调用者无法区分"我正在寻找的东西不在那里"和"我问了一个无效的问题."

因为Optional是新的,所以也有过度使用的倾向; 希望随着时间的推移,正确的模式将被内化.

可选是null对象模式的示例; 当"没有任何东西"是一个合理的预期结果时,它提供了一种安全的方式来说"没有任何东西".(返回空数组或空集合在这些域中是类似的例子.)无论你是想用null/optional表示"什么都没有"vs一个异常通常是"没有任何东西"是否是一个普遍预期的情况,或者是否特殊.例如,Map.get如果映射不存在,则没有人想要抛出异常; 映射不存在是预期的,而不是例外的结果.(如果我们Optional在1997年,Map.get可能已经返回了Optional.)

我不知道你在哪里听到的建议是,Optional比例外更可取,但是谁告诉你这是不明智的.如果你之前抛出异常,你可能仍然会抛出异常; 如果您之前返回null,则可以考虑返回Optional.

  • @TomášZáluský要求添加到Optional中的一组"明显"方法看似无穷无尽. (3认同)
  • 在切线上,目前是否有任何关于可选的积极优化的工作?比如链接`filter`调用的短路?我发现无条件调用是降低使用Optional实现的`equals`性能的关键因素. (2认同)
  • @MarkoTopolnik目前不在; 我们将优化工作投入更大的目标. (2认同)

小智 5

在可能出现错误的情况下,合适的数据类型是 Try。

Try 不使用抽象“present”或“empty”,而是使用抽象“failure”或“success”。

由于 Java 8 没有提供 Try 开箱即用,因此需要使用一些 3. 方库。(也许我们会看到它添加到 Java 9 中?)

试试 Java