38

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.

Boann
  • 48,794
  • 16
  • 117
  • 146
ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155
  • 8
    Seems, it does not realize that this is unnecessary for `this`, compared to other expressions. – Holger Jun 12 '20 at 08:52
  • 1
    good catch(+1). to add to it, the same behavior can be observed with `javac 15-ea` as well. further observation, removing `final` works fine. – Naman Jun 12 '20 at 08:55
  • trying to fit in a comment, `int sum();... Code: stack=2, locals=1, args_size=1 0: iconst_1 1: aload_0 2: invokestatic #13 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object; 5: pop 6: iconst_5 7: iadd 8: ireturn` versus when removing the final keyword resolving to `int sum();... Code: stack=2, locals=1, args_size=1 0: iconst_1 1: aload_0 2: getfield #7 // Field field:I 5: iadd 6: ireturn` – Naman Jun 12 '20 at 09:03

1 Answers1

41

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 2
    Good point. It's even easier to demonstrate like this: `Temp t = null; return 1 + t.field`. `Objects.requireNonNull(t)`, otherwise it would return 6 without NPE. – ZhekaKozlov Jun 12 '20 at 09:22
  • Is there not also the fact, that an other class could use use reflection to set the field to null at any point in time? (Final is a language construct, not a runtime constraint). – MTilsted Jun 12 '20 at 21:39
  • 1
    @MTilsted: No, because in this case the field itself is a primitive `int`, which cannot be null. – Ilmari Karonen Jun 12 '20 at 22:20
  • 1
    @MTilsted changing final fields with reflection can result in undefined behavior. Potentially not affecting inlined values is one of them. – k5_ Jun 12 '20 at 22:46
  • 1
    @MTilsted the check is not about the field value. It is about the field owner reference, i.e. `this`. The reference cannot be changed via Reflection and it is impossible to be `null`, because it is impossible to enter an instance method with a `null` receiver reference; the invocation instruction would already fail. – Holger Jun 15 '20 at 07:00
  • There's a policy that `javac` doesn't do optimisation. It used to but made it more complicated and, I believe, hindered HotSpot. I think there was a JVM change that made `getClass` undesirable (I forget what), but it and `Objects.requireNonNull` are HotSpot intrinsics. – Tom Hawtin - tackline Sep 12 '20 at 19:59
  • 1
    @TomHawtin-tackline the transition from using `getClass` to `requireNonNull` has been addressed in [this Q&A](https://stackoverflow.com/q/43115645/2711488). – Holger Sep 13 '20 at 09:11