与lambdas中的Class.isInstance和Class.cast方法引用相比,instanceOf和()强制转换

时间:2018-04-20 07:57:23

标签: java lambda refactoring method-reference

在重构一些代码时,我遇到了这段代码

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是内部进程,我可能还有其他负面影响吗? 在我看来,它仍然像以前一样可读。任何想法都是最受欢迎的。

3 个答案:

答案 0 :(得分:1)

一旦JIT编译器完成其工作,这些构造之间的性能差异通常可以忽略不计。所以,正如其他人所说,更喜欢你认为更具可读性的东西。

如果我们查看性能,在将代码优化到最大值之前,结果有时可能会违反直觉。

通常,与调用相同方法的lambda表达式相比,方法引用在创建站点生成的代码更少,并且委托级别更少,但是在这里,lambda表达式没有做同样的事情。

o -> o instanceof MyClassBo -> (MyClassB)o之类的Lambda表达式是不变的,对类MyClassB的引用包含在合成目标方法的代码中。相反,方法引用MyClassB.class::isInstanceMyClassB.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优化。

所以我的结论是几乎没有差异,也不会产生负面影响。因此,您应该使用最易读且易于理解的方法。