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.
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 aNullPointerException
is thrown.- If the field is a non-blank
final
, then the result is the value of the named member field in typeT
found in the object referenced by the value of the Primary.…