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.

Holger
  • 285,553
  • 42
  • 434
  • 765
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 4
    Question from Peter Lawrey, answer by Holger, bug opened by Shipilev; this question made my day for sure. – Eugene Mar 30 '17 at 12:55

1 Answers1

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
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thank you, your insight is valuable as always. – Peter Lawrey Mar 30 '17 at 11:57
  • It might be nice if there were simply a nullcheck byte code instruction. – David Conrad Mar 30 '17 at 13:26
  • 1
    @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. – Holger Mar 30 '17 at 13:31
  • @Holger 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. – holi-java Mar 30 '17 at 14:12
  • 1
    @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](http://ideone.com/wJe5Va), the cited `JDK-8073550` is already linked, and if you want go deeper, you might start with [this mail](http://mail.openjdk.java.net/pipermail/core-libs-dev/2015-February/031631.html) and follow the discussion. – Holger Mar 30 '17 at 15:15
  • Java 14 includes the new feature [Helpful NullPointerExceptions](https://openjdk.java.net/jeps/358). 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 null` – Lii Apr 07 '20 at 18:47
  • 1
    @Lii I noticed that [`NullPointerException.getMessage()`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/NullPointerException.html#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. – Holger Apr 08 '20 at 09:23