我只是偶然发现了Java 8中的Optional类 - 我真的喜欢在我的代码中使用isPresent()方法调用替换一些空检查(字面意思是“值是否存在?”)的方法。
我的问题是:这不会导致我的代码性能下降吗?我只是猜测简单的空检查可能会有点便宜,而且我在字节码读取/解释方面还不是很好,所以我真的对你对这个主题的想法很感兴趣。
我只是偶然发现了Java 8中的Optional类 - 我真的喜欢在我的代码中使用isPresent()方法调用替换一些空检查(字面意思是“值是否存在?”)的方法。
我的问题是:这不会导致我的代码性能下降吗?我只是猜测简单的空检查可能会有点便宜,而且我在字节码读取/解释方面还不是很好,所以我真的对你对这个主题的想法很感兴趣。
Optional<T>
它只是一个普通的泛型类,它包含类型为T的引用。因此,它添加了一个间接层。这个方法调用自己也不会很贵,因为类是 final
因此可以避免动态调度。
唯一可能遇到性能问题的地方是在处理大量此类实例时,但即使这样,也会出现类似的情况 Stream<Optional<String>>
一点都不差。但是,在处理大量原始值时,您会发现使用的性能损失 Stream<Integer>
(要么 Integer[]
与原始专业化相比 IntStream
(要么 int[]
由于这个间接层需要非常频繁的实例化 Integer
对象。然而,这是我们已经知道并且在使用诸如此类之类的东西时付出的代价 ArrayList<Integer>
。
你显然会经历同样的打击 Stream<OptionalInt>
/ OptionalInt[]
因为OptionalInt基本上是一个带有的类 int
领域和 boolean
存在的标志(不同于 Optional<T>
这只能与之相关 T
场)因此非常相似 Integer
虽然尺寸更大。当然,还有 Stream<Optional<Integer>>
会添加 二 间接级别,具有相应的双重性能损失。
我使用大量使用空检查的算法以及对可能为空的字段的访问进行了一些性能测试。我实现了一个简单的算法,从单个链表中删除中间元素。
首先,我实现了两类链表节点:safe - with Optional和unsafe - without。
安全节点
class Node<T> {
private final T data;
private Optional<Node<T>> next = Optional.empty();
Node(T data) {
this.data = data;
}
Optional<Node<T>> getNext() {
return next;
}
void setNext(Node<T> next) { setNext(Optional.ofNullable(next)); }
void setNext(Optional<Node<T>> next ) { this.next = next; }
}
不安全的节点
class NodeUnsafe<T> {
private final T data;
private NodeUnsafe<T> next;
NodeUnsafe(T data) {
this.data = data;
}
NodeUnsafe<T> getNext() {
return next;
}
void setNext(NodeUnsafe<T> next) {
this.next = next;
}
}
然后我实现了两个类似的方法,唯一的区别 - 第一次使用 Node<T>
第二个用途 NodeUsafe<T>
class DeleteMiddle {
private static <T> T getLinkedList(int size, Function<Integer, T> supplier, BiConsumer<T, T> reducer) {
T head = supplier.apply(1);
IntStream.rangeClosed(2, size).mapToObj(supplier::apply).reduce(head,(a,b)->{
reducer.accept(a,b);
return b;
});
return head;
}
private static void deleteMiddle(Node<Integer> head){
Optional<Node<Integer>> oneStep = Optional.of(head);
Optional<Node<Integer>> doubleStep = oneStep;
Optional<Node<Integer>> prevStep = Optional.empty();
while (doubleStep.isPresent() && doubleStep.get().getNext().isPresent()){
doubleStep = doubleStep.get().getNext().get().getNext();
prevStep = oneStep;
oneStep = oneStep.get().getNext();
}
final Optional<Node<Integer>> toDelete = oneStep;
prevStep.ifPresent(s->s.setNext(toDelete.flatMap(Node::getNext)));
}
private static void deleteMiddleUnsafe(NodeUnsafe<Integer> head){
NodeUnsafe<Integer> oneStep = head;
NodeUnsafe<Integer> doubleStep = oneStep;
NodeUnsafe<Integer> prevStep = null;
while (doubleStep != null && doubleStep.getNext() != null){
doubleStep = doubleStep.getNext().getNext();
prevStep = oneStep;
oneStep = oneStep.getNext();
}
if (prevStep != null) {
prevStep.setNext(oneStep.getNext());
}
}
public static void main(String[] args) {
int size = 10000000;
Node<Integer> head = getLinkedList(size, Node::new, Node::setNext);
Long before = System.currentTimeMillis();
deleteMiddle(head);
System.out.println("Safe: " +(System.currentTimeMillis() - before));
NodeUnsafe<Integer> headUnsafe = getLinkedList(size, NodeUnsafe::new, NodeUnsafe::setNext);
before = System.currentTimeMillis();
deleteMiddleUnsafe(headUnsafe);
System.out.println("Unsafe: " +(System.currentTimeMillis() - before));
}
}
两个具有不同大小的列表的比较显示了这种方法 使用的代码 Optional
在最好的情况下,比使用nullables的情况慢两倍。使用小列表它慢3倍。
我们使用openjdk标记下面的代码。
sc.map(MYObject::getRequest)
.map(RequestDO::getMyInst)
.map(MyInstDO::getCar)
.map(CarDO::getId);
if(id.isPresent())
要么
if( null != MYObject.getRequest() && null !=
MYObject.getRequest().getMyInst() && null !=
MYObject.getRequest().getMyInst().getCar() && null !=
MYObject.getRequest().getMyInst().getCar().getId() )
结果显示Optional比传统的非null检查要好得多。
Benchmark Mode Cnt Score Error Units
JMHBMarkModes.measureNotNull thrpt 5 0.149 ± 0.036 ops/us
JMHBMarkModes.measureOptional thrpt 5 11.418 ± 1.140 ops/us
JMHBMarkModes.measureNotNull avgt 5 12.342 ± 8.334 us/op
JMHBMarkModes.measureOptional avgt 5 0.088 ± 0.010 us/op
但是如果你的用例是这样的话 (null != MYObject.getRequest())
,那么不是空检查更好。所以可选性能取决于您的用例。