In Java Lambda's why is getClass() called on a captured variable
Asked Answered
S

1

37

If you look at the byte code for

Consumer<String> println = System.out::println;

the byte code generates by Java 8 update 121 is

GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer; [
  // handle kind 0x6 : INVOKESTATIC
  java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  // arguments:
  (Ljava/lang/Object;)V, 
  // handle kind 0x5 : INVOKEVIRTUAL
  java/io/PrintStream.println(Ljava/lang/String;)V, 
  (Ljava/lang/String;)V
]
ASTORE 1

The getClass() method is being called on the System.out and the result is ignored.

Is this an indirect null reference check?

Certainly if you run

PrintStream out = null;
Consumer<String> println = out::println;

This triggers a NullPointerException.

Spurt answered 30/3, 2017 at 10:47 Comment(1)
Question from Peter Lawrey, answer by Holger, bug opened by Shipilev; this question made my day for sure.Rawlinson
D
35

Yes, calling getClass() has become a canonical “test for null” idiom, as getClass() is expected to be a cheap intrinsic operation and, I suppose, HotSpot might be capable of detecting this pattern and reduce the operation to an intrinsic null-check operation, if the result of getClass() is not used.

Another example is creating an inner class instance with an outer instance that is not this:

public class ImplicitNullChecks {
    class Inner {}
    void createInner(ImplicitNullChecks obj) {
        obj.new Inner();
    }

    void lambda(Object o) {
        Supplier<String> s=o::toString;
    }
}

compiles to

Compiled from "ImplicitNullChecks.java"
public class bytecodetests.ImplicitNullChecks {
  public bytecodetests.ImplicitNullChecks();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void createInner(bytecodetests.ImplicitNullChecks);
    Code:
       0: new           #23                 // class bytecodetests/ImplicitNullChecks$Inner
       3: dup
       4: aload_1
       5: dup
       6: invokevirtual #24                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
       9: pop
      10: invokespecial #25                 // Method bytecodetests/ImplicitNullChecks$Inner."<init>":(Lbytecodetests/ImplicitNullChecks;)V
      13: pop
      14: return

  void lambda(java.lang.Object);
    Code:
       0: aload_1
       1: dup
       2: invokevirtual #24                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
       5: pop
       6: invokedynamic #26,  0             // InvokeDynamic #0:get:(Ljava/lang/Object;)Ljava/util/function/Supplier;
      11: astore_2
      12: return
}

See also JDK-8073550:

A few places in our class library use the weird trick of using object.getClass() to check for nullity. While this make seem a smart move, it actually confuses people into believing this is an approved practice of null checking.

With JDK 7, we have Objects.requireNonNull that provide the proper null checking, and declare the intent properly.

It might be debatable whether this should apply to programming language intrinsic checks as well, as using Objects.requireNonNull for that purpose would create a dependency to a class outside the java.lang package not visible in the source code. And in this specific case, the trick is only visible to those who look at the byte code. But it has been decided to change the behavior with Java 9.

This is how jdk1.9.0b160 compiles the same test class:

Compiled from "ImplicitNullChecks.java"
public class bytecodetests.ImplicitNullChecks {
  public bytecodetests.ImplicitNullChecks();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void createInner(bytecodetests.ImplicitNullChecks);
    Code:
       0: new           #26                 // class bytecodetests/ImplicitNullChecks$Inner
       3: dup
       4: aload_1
       5: dup
       6: invokestatic  #27                 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
       9: pop
      10: invokespecial #28                 // Method bytecodetests/ImplicitNullChecks$Inner."<init>":(Lbytecodetests/ImplicitNullChecks;)V
      13: pop
      14: return

  void lambda(java.lang.Object);
    Code:
       0: aload_1
       1: dup
       2: invokestatic  #27                 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
       5: pop
       6: invokedynamic #29,  0             // InvokeDynamic #0:get:(Ljava/lang/Object;)Ljava/util/function/Supplier;
      11: astore_2
      12: return
}
Dwaynedweck answered 30/3, 2017 at 11:34 Comment(7)
Thank you, your insight is valuable as always.Spurt
It might be nice if there were simply a nullcheck byte code instruction.Eadith
@David Conrad: it’s not needed that often and the advantage of a dedicated check instruction over a single invocation instruction (to a well known method) is not that big. As a net benefit, if the JVM recognizes Objects.requireNonNull and handles these invocations intrinsically, Java code calling it explicitly will benefit from it as well.Dwaynedweck
@Dwaynedweck first,vote up!nice question and answer.Hi,Holger. Is there a external reference for your answer?due to my english I want to see more details.Thule
@holi-java: I’m not sure, what kind of external reference you expect. The discussed compiled form can be verified by everyone, e.g. using this class on Ideone, the cited JDK-8073550 is already linked, and if you want go deeper, you might start with this mail and follow the discussion.Dwaynedweck
Java 14 includes the new feature Helpful NullPointerExceptions. Indeed, when evaluation a method reference on a null instance the message in the resulting exception is this: Cannot invoke "Object.getClass()" because "c" is nullEpicontinental
@Epicontinental I noticed that NullPointerException.getMessage() has been overridden. Unfortunately, it wouldn’t be helpful here, unless this feature treats Objects.requireNonNull specially and analyses the caller. The outer.new Inner() example is right here, in my answer.Dwaynedweck

© 2022 - 2024 — McMap. All rights reserved.