Optional,Gauva工具及和Java8中实现的区别
Guava的狂热者已经处理Optional了一段时间的概念(实际上从Guava10开始,已经有5年了)。和往常一样,我不会谈论如何使用Optional,我对Google实现该概念的方式,Java 8实现以及两者之间的差异更感兴趣。
花费了两年半的时间来感觉到JDK中的这种差距,并且JDK专家组所做的选择不仅在实现方面很有趣,而且在命名约定方面也很有趣。
比较区别
但是,让我们从Guava实现开始。
一、Guava中的实现
Optional是一个抽象类,实现Serializable。它可能看起来像是一个很小的细节,但事实并非如此,在讨论Java 8实现时,我将回到这一点。
public abstract class Optional<T> implements Serializable {
public abstract boolean isPresent();
public abstract T get();
public abstract T or(T defaultValue);
....
}
Guava团队决定分裂的概念Optional,是空的或不,创建了两个不同的实现:Absent和Present。
这些类不直接访问,默认的构造函数Optional已经被限制到包中,底层的实现不提供任何公共构造函数,他们只是继承了一个从Optional它不可访问,并强制执行此,这些类Absent和Present仅在包级别可见:
final class Absent extends Optional<Object> { .... }
final class Present<T> extends Optional<T> { .... }
Optional在这一点上非常哑类,所有的实例方法是抽象的(isPresent,get,or…),唯一的实现是建立一个一Optional:
public static <T> Optional<T> of(T value) {
return new Present<T>(checkNotNull(value));
}
public static <T> Optional<T> fromNullable(@Nullable T value) { return (value == null) ? Optional<T>.absent() : new Present<T>(value);
}
该of(T reference)方法将始终返回一个Present对象,如果参数为null,NullPointerException则将引发a。
在fromNullable将返回Absent如果参数为空对象,否则Present。到目前为止,没有什么花哨的,有趣的部分在于Absent和之间的分隔Present。
根据定义,它Optional是一个不可变的对象,考虑到这一点,该方法没有理由isPresent()每次都要检查中的值Optional是否为null,因为在两次调用之间它不能更改。
因此,一旦Absent构建了对象,它将始终表示值的缺失,方法的实现get()或isPresent()变得非常简单:
@Override
public boolean isPresent() {
return false;
}
@Override
public Object get() {
throw new IllegalStateException("value is absent");
}
我们可能认为这是一个优化问题,但是今天JVM将优化这样的实现:
public boolean isPresent() {
return value != null;
}
价值是最终的,现代JVM可以通过方法内联和JIT编译轻松地优化这种代码。因此,最终,这是概念的分离而不是真正的优化。
Absence每个JVM 只有一个对象实例(在加载类时实例化的static字段Absence)。Optional从空引用创建Optional时,始终使用此实例。这意味着您表示Optional还是都没有关系Optional,如果您Optional为空,则两个变量都将指向同一实例。
实现Present遵循相同的模式,不变性提供了一条简单的路径,该值永远不会为null:
@Override
public boolean isPresent() {
return true;
}
@Override
public T get() {
return value;
}
像任何其他Java对象一样,Optional提供hashCode和equals方法。hashCode有趣的是,Absence对象总是返回相同的魔术常数:0x598df91c,但是,对于Present,公式是该魔术常数和hashCode的值的组合:
@Override
public int hashCode() {
return 0x598df91c + value.hashCode();
}
我猜想,他们想通过不hashCode()直接依赖值的形式来区分值本身和包装在容器中的值的概念。
二、Java8中的实现
Java 8实现非常简单。与Guava相反,没有概念分离。该java.util.Optional是一个final类继承java.lang.Object。
就像Guava一样,构造函数是不可见的,Optional通过两个主要的静态方法来构建gets:
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
即使从潜在的空引用获取实例的方法命名上的差别很小,该设计也使得从Guava到Java的重构非常容易。
我们可以找到共同的方法,例如get(),isPresent()。他们用明显empty()返回空值的方法Optional(以absent()Guava 命名)表示缺少值。
到目前为止,在实现中没有什么花哨的:
public boolean isPresent() {
return value != null;
}
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
有趣的部分在于您可以从JDK Optional与功能编程范例进行交互。就像Guava一样,它提供了方法or(Supplier<? extends T> supplier)(实际上orElseGet在JDK中)和transform(map在JDK中)。但是JDK在filter和方面走得更远flatMap:
public Optional<T> filter(Predicate<? super T> predicate)
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
Predicate并且Function是Java 8中引入的新功能接口,但是这些接口从一开始就已经在Guava中可用(实际上是2.0版),我很惊讶它们没有提供filter和flatMap。在他们发布该Optional功能后,甚至变换方法也需要8个月的时间在Guava中引入。您可能会说flapMap和之间的差异map很小,只是映射器Optional直接返回了一个,而在实现和维护方面却不花任何钱。
关于hashCode,他们采取了不同的方向。它们没有将包装在容器中的值的概念与值本身分开,它们直接依赖于hashCode值的:
@Override
public int hashCode() {
return Objects.hashCode(value);
}
这意味着如果的Optional值为空(值== null),0则将返回,否则将直接返回的hashCode。
三、Serializable
在Java 8和Guava实现(在Java 8的功能编程定位旁边之间的主要区别map,flatMap而filter)是Java专家小组的决定不会让Optional serializable(违背Guava一个)。这个决定引发了其他互联网的激烈辩论。这意味着,如果您使用序列化和Guava Optional,则向JDK Optional的迁移将破坏您的序列化系统java.io.NotSerializableException。
为什么要这样决定?到目前为止,我可以看到两个答案:一个是从技术/维护的角度来看,另一个是从概念的角度(他们认为Optional应该使用的方式)。
在精心设计JSR-335(Lambda表达式,Stream,可选绑定…)期间,有人想出了该名称,OptionalReturn以强制执行该功能的设计方向。是的,他们认为Optional应该仅支持可选返回成语。它并不是要用作类的字段。这就是为什么在设计/概念角度上Optional没有标记的原因Serializable。但是,即使您决定使用Optional,也只能在optional-return模式的上下文中,不能通过RMI使用您的方法。
关于技术原因,我将让Brian Goetz(Oracle的Java语言架构师)给出解释。如果有人比我们更了解,那就可能是他,这是他在邮件讨论中的报价:
在JDK可序列化中进行处理会使我们的维护成本急剧增加,因为这意味着该表示将一直冻结。这限制了我们将来开发实现的能力,而我们无法轻松修复错误或提供增强功能的情况数量之多,否则情况将非常简单。
除了功能编程的方向外,我认为Optional所有这些都与提高可读性和执行公共接口合同有关。当您处理私有类字段时,由您来管理幕后发生的事情。将其用作类字段并不一定是一个坏习惯,但是这样的模式也不会出错,而且至少还是可序列化的:
@Nullable private final String flightNumber;
public Optional<String> getFlightNumber() {
return Optional.ofNullable(this.flightNumber)
}
关于它们带来的改进,再加上功能编程,它们是一个非常令人兴奋的概念。