2

While refactoring some code I came across this piece of code

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 */)

The actual piece of code was a lot bigger, but since I like to use method references where possible, I refactored the stream into this:

list.stream()
    .filter(MyClassB.class::isInstance)
    .map(MyClassB.class::cast)
    .forEach(/* do something */)

I am now wondering if this is in any way more efficient? More advisable? I think that Java generates less code using method references but could there be other negative implications I'm not thinking of since checking for instanceOf and casting are rather internal processes?
In my opinion it will still be as readable as before. Any thoughts are most welcome.

Sebastian
  • 5,177
  • 4
  • 30
  • 47
  • Thing with performance in Java is: the JIT compiler does what he deems best. Even if one thing or another generates less (byte-)code, this could be drastically changed through hotspot compilation. As to the negative implications... there are none I am aware of. That said, I would adivce for readability and homogenity. If you are working in a team, ask your teamcollegues – Turing85 Apr 20 '18 at 08:07

3 Answers3

3

The performance difference between such constructs usually is negligible, once the JIT compiler has done its work. So, as said by others, prefer what you consider more readable.

If we look at the performance, before the code has been optimized to its maximum, the outcome can be counter-intuitive sometimes.

Generally, method references produce less code at the creation site and one less level of delegation compared to a lambda expression calling the same method, but here, the lambda expression isn’t doing the same thing.

Lambda expressions like o -> o instanceof MyClassB and o -> (MyClassB)o are invariant and the reference to the class MyClassB is contained in the code of the synthetic target method. In contrast, the method references MyClassB.class::isInstance and MyClassB.class::cast refer to the method Class.isInstance resp. Class.cast and the actual receiver of the method invocation is captured when the instance of the functional interface is created. The current implementation is not capable of detecting that the receiver instance will always be the same, so it will create a new object every time when the instance of Predicate resp. Function is requested. In contrast, for the non-capturing lambda expression, it will reuse the instance.

So unless the optimizer inlined the entire stream pipeline and successfully applied Escape Analysis, the method references may produce more object instances, further these are instances of a generated class that is not tied to a particular class to test or cast to, but have the actual Class in an instance field. This may have performance drawbacks compared to the lambda expressions. But in practice, you will rarely notice.

Holger
  • 285,553
  • 42
  • 434
  • 765
0

The difference between those approaches is that the latter approach allows for dynamic operations, whereas the first ones are static.

Both will do the same thing, but there are cases where you just can't use the first approach. The optimal solution would be to get rid of the casts altogether, but if you insist on doing it, the choice is up to you. Any performance considerations are irrelevant as with all unwarranted micro-optimization.

It can cause confusion to use Class::cast when there isn't a real need for it though.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
0

Generally you should optimize for readability in cases like this. The performance differences should be so minimal that it almost always makes no sense to optimize for performance in cases like that.

But lets try to take a look at the various changes you are doing here:

  • Like you already mention a method reference should generate less byte code. Also there should be one less method call into the anonymous method the compiler generates for a lambda. Some of the technical differences are discussed here.

  • According to the documentation of isInstance():

    This method is the dynamic equivalent of the Java language instanceof operator.

    Additionally there is a Hotspot compiler intrinsic for this method, therefore it should be replaced by a specially optimized builtin function. Very likely the same function as used with instanceof. What's faster: instanceof or isInstance?

  • The source code of cast(), is doing exactly what you did before in the lambda. So no obvious difference here, just an extra method call that can likely also be optimized by the jit.

So my conclusion would be that there is almost no difference and there should be no negative implications. You should therefore use the most readable and understandable approach.

Gregor Koukkoullis
  • 2,255
  • 1
  • 21
  • 31