可选 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 年有 Optional
,Map.get
可能会 return 编辑 Optional
。)
我不知道您从哪里听说过 Optional 比 exceptions 更可取的建议,但告诉您的人是错误的。如果你之前抛出异常,你可能仍然应该抛出异常;如果您之前 returned null,您可以考虑 returning Optional
。
在可能出现错误的情况下,合适的数据类型是 Try。
Try 不使用抽象 'present' 或 'empty',而是使用抽象 'failure' 或 'success'。
由于Java8开箱不提供Try,需要使用一些3.方库。 (也许我们会在 Java 9 中看到它?)
从 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 年有 Optional
,Map.get
可能会 return 编辑 Optional
。)
我不知道您从哪里听说过 Optional 比 exceptions 更可取的建议,但告诉您的人是错误的。如果你之前抛出异常,你可能仍然应该抛出异常;如果您之前 returned null,您可以考虑 returning Optional
。
在可能出现错误的情况下,合适的数据类型是 Try。
Try 不使用抽象 'present' 或 'empty',而是使用抽象 'failure' 或 'success'。
由于Java8开箱不提供Try,需要使用一些3.方库。 (也许我们会在 Java 9 中看到它?)