在重构一些代码时,我遇到了这段代码
class MyClassB extends MyClassA {...}
// other classes extending from MyClassA
...
List<MyClassA> list = new ArrayList<>();
// fill list
list.stream()
.filter(o -> o instanceof MyClassB)
.map(o -> (MyClassB)o)
.forEach(/* do something */)
实际的代码段要大得多,但由于我喜欢尽可能使用方法引用,我将流重构为:
list.stream()
.filter(MyClassB.class::isInstance)
.map(MyClassB.class::cast)
.forEach(/* do something */)
我现在想知道这是否更有效率?更明智的是什么?我认为Java使用方法引用生成的代码较少,但是由于检查instanceOf和cast是内部进程,我可能还有其他负面影响吗? 在我看来,它仍然像以前一样可读。任何想法都是最受欢迎的。
答案 0 :(得分:1)
一旦JIT编译器完成其工作,这些构造之间的性能差异通常可以忽略不计。所以,正如其他人所说,更喜欢你认为更具可读性的东西。
如果我们查看性能,在将代码优化到最大值之前,结果有时可能会违反直觉。
通常,与调用相同方法的lambda表达式相比,方法引用在创建站点生成的代码更少,并且委托级别更少,但是在这里,lambda表达式没有做同样的事情。
o -> o instanceof MyClassB
和o -> (MyClassB)o
之类的Lambda表达式是不变的,对类MyClassB
的引用包含在合成目标方法的代码中。相反,方法引用MyClassB.class::isInstance
和MyClassB.class::cast
引用方法Class.isInstance
resp。创建功能接口的实例时,Class.cast
并且方法调用的实际接收方捕获。当前实现无法检测到接收器实例将始终相同,因此每次Predicate
实例时它都会创建一个新对象。请求Function
。相反,对于非捕获lambda表达式,它将重用该实例。
因此,除非优化器内联整个流管道并成功应用Escape Analysis,否则方法引用可能会生成更多对象实例,而且这些实例类是生成的类的实例,它不依赖于要测试或强制转换的特定类,但是在实例字段中包含实际的Class
。与lambda表达式相比,此可能具有性能缺陷。但在实践中,你很少会注意到。
答案 1 :(得分:0)
这些方法之间的区别在于后一种方法允许dynamic operations,而第一种方法是静态的。
两者都会做同样的事情,但有些情况下你不能使用第一种方法。最佳解决方案是完全摆脱演员阵容,但如果你坚持这样做,那么选择取决于你。任何性能考虑都与所有无根据的微优化无关。
虽然在没有真正需要的情况下使用Class::cast
会导致混淆。
答案 2 :(得分:0)
通常,在这种情况下,您应该优化可读性。性能差异应该是如此之小,以至于在这种情况下优化性能几乎总是没有意义。
但是让我们试着看看你在这里做的各种改变:
就像你已经提到的方法引用应该生成更少的字节代码。此外,编译器为lambda生成的匿名方法应该少一个方法调用。 讨论了一些技术差异here。
根据isInstance()的文档:
此方法是Java语言instanceof的动态等价物 操作
此外,此方法还有一个内在的Hotspot编译器,因此应该用特别优化的内置函数替换它。很可能与instanceof使用的功能相同。 What's faster: instanceof or isInstance?
cast()的源代码正在完成您之前在lambda中所做的工作。所以这里没有明显的区别,只是一个额外的方法调用,也可能由jit优化。
所以我的结论是几乎没有差异,也不会产生负面影响。因此,您应该使用最易读且易于理解的方法。