Search code examples
javajava-8javacjava-11ecj

Why does javac insert Objects.requireNonNull(this) for final fields?


Consider the following class:

class Temp {
    private final int field = 5;

    int sum() {
        return 1 + this.field;
    }
}

Then I compile and decompile the class:

> javac --version
javac 11.0.5

> javac Temp.java

> javap -v Temp.class
  ...
  int sum();
    descriptor: ()I
    flags: (0x0000)
    Code:
      stack=2, locals=1, args_size=1
         0: iconst_1
         1: aload_0
         2: invokestatic  #3   // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
         5: pop
         6: iconst_5
         7: iadd
         8: ireturn

In simple words, javac compiles sum() to this:

int sum() {
    final int n = 1;
    Objects.requireNonNull(this); // <---
    return n + 5;
}

What is Objects.requireNonNull(this) doing here? What's the point? Is this somehow connected to reachability?

The Java 8 compiler is similar. It inserts this.getClass() instead of Objects.requireNonNull(this):

int sum() {
    final int n = 1;
    this.getClass(); // <---
    return n + 5;
}

I also tried to compile it with Eclipse. It doesn't insert requireNonNull:

int sum() {
    return 1 + 5;
}

So this is javac-specific behavior.


Solution

  • Since the field is not only final, but a compile-time constant, it will not get accessed when being read, but the read gets replaced by the constant value itself, the iconst_5 instruction in your case.

    But the behavior of throwing a NullPointerException when dereferencing null, which would be implied when using a getfield instruction, must be retained¹. So when you change the method to

    int sumA() {
      Temp t = this;
      return 1 + t.field;
    }
    

    Eclipse will insert an explicit null check too.

    So what we see here, is javac failing to recognize that in this specific case, when the reference is this, the non-null property is guaranteed by the JVM and hence, the explicit null check is not necessary.

    ¹ see JLS §15.11.1. Field Access Using a Primary:

    • If the field is not static:

      • The Primary expression is evaluated. If evaluation of the Primary expression completes abruptly, the field access expression completes abruptly for the same reason.
      • If the value of the Primary is null, then a NullPointerException is thrown.
      • If the field is a non-blank final, then the result is the value of the named member field in type T found in the object referenced by the value of the Primary.