可选 vs 抛出异常

Optional vs throwing an exception

从 Java 1.8 开始,返回 Optional 对象比抛出异常更可取,这是真的吗? 我越来越多地看到这样的代码:

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

而不是这个:

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

这是否意味着我们需要忘记旧方法并使用新方法? Optional 在哪里合适?

您提供的示例不是可选的适当用法。空的 Optional 表示由于调用者无法预测的原因而缺失的值。它是方法合法调用的结果。

您作为 "old idiom" 呈现的代码执行输入验证,如果输入无效则抛出未经检查的异常。即使您引入 Optional,此行为也应保持不变。唯一的区别是 Object get(i) 的 return 值可能为空,而 Optional<?> get(i) 的 return 值永远不会为空,因为 Optional 实例的特殊状态表示没有值。

使用 return 可选值而不是可空值的方法的优点是消除了样板代码,样板代码在尝试对 returned 值执行任何操作之前必须进行例行空值检查。纯粹在方法中使用 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]);

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

出于兴趣,您可以完全根据 Optional:

实现 equals
@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();
}

虽然我觉得这个习语非常简洁易读,但目前还没有优化到足以推荐作为生产价值的选择。不过,Java 的未来版本可能会实现这一点。

同样可以滥用异常、空值和可选值。在这种特殊情况下,我认为您可能在滥用可选,因为您默默地隐藏了先决条件违规并将其转换为正常的 return。从您的代码中收到一个空的可选值后,调用者无法区分 "the thing I was looking for wasn't there" 和 "I asked an invalid question."

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

可选的是空对象模式的一个例子;当 "nothing there" 是合理的预期结果时,它提供了一种安全的方式来表示 "nothing was there"。 (返回空数组或空集合是这些领域中的类似示例。)是否要用 null/optional 表示 "nothing there" 与异常通常取决于 "nothing there" 是否是一个常见的函数预期的情况,或者是否异常。例如,没有人希望 Map.get 在映射不存在时抛出异常; mapping-not-present 是预期的结果,并非例外。 (如果我们在 1997 年有 OptionalMap.get 可能会 return 编辑 Optional。)

我不知道您从哪里听说过 Optional 比 exceptions 更可取的建议,但告诉您的人是错误的。如果你之前抛出异常,你可能仍然应该抛出异常;如果您之前 returned null,您可以考虑 returning Optional

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

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

由于Java8开箱不提供Try,需要使用一些3.方库。 (也许我们会在 Java 9 中看到它?)

Try for Java