Does javac cleanup useless type casts?
Often, writing code requires to test assumptions. The easiest way is often to do some research on the net. But when the first few queries do not return a useful result, it is time to test the assumption by ourselves.
In my case, I was wondering whether explicitly casting a variable to its static type introduces a cast in the compiled bytecode1.
For instance, in the program below the cast from int
to int
is useless and could be removed during compilation.
int a = 1;
System.out.println(((int) a) + 1);
Of course, if written by hand, the best design decision is to simply remove the cast and to continue coding. But, in my case, the Java code is generated by a compiler, which left me two choices. Either add some logic to my compiler and remove useless casts during code generation. Or, left them in place and let the Java compiler do the heavy lifting.
I’d rather avoid complexifying my compiler for now, and I’d be happier with a few useless casts in the generated Java source code, but only if they are finally removed from the Java bytecode.
This is why I want to validate the following question:
Does javac cleanup useless type cast?
I based my experiment on the sample below, where a callMe(B b)
method is called three times on different configurations.
The first call is our baseline, what one would write manually. The second call introduce a useless cast since b
is already statically of type B
.
Finally, the third does a down-cast of a
to type B
.
Only the later is expected to produce a cast operation in the compiled bytecode.
public class Demo {
static class A {}
static class B extends A {}
private static void callMe(B b) {}
public static void main(String[] args) {
B b = new B();
A a = new B();
callMe(b); // nominal method invocation
callMe((B) b); // useless cast
callMe((B) a); // useful cast
}
}
Compiling the java classes is straightforward and lead to the compilation of three classes: WithCasts
and the two inner classes WithCasts$A
and WithCasts$B
.
$ mkdir -p bin
$ javac -d bin WithCasts.java
$ tree
.
├── bin
│ ├── WithCasts$A.class
│ ├── WithCasts$B.class
│ └── WithCasts.class
└── WithCasts.java
Now, time to inspect the bytecode using javap
2.
I won’t go into the details of how to read bytecode, but I found it useful to get enough knowledge to be minimally knowledgeable on the topic3. The bytecode below is annotated in order to ease its reading.
$ javap -cp bin -c WithCasts
Compiled from "WithCasts.java"
public class WithCasts {
public WithCasts();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void callMe(WithCasts$B);
Code:
0: return
public static void main(java.lang.String[]);
Code:
# B b = new B();
0: new #2 // class WithCasts$B
3: dup
4: invokespecial #3 // Method WithCasts$B."<init>":()V
7: astore_1
# A a = new B();
8: new #2 // class WithCasts$B
11: dup
12: invokespecial #3 // Method WithCasts$B."<init>":()V
15: astore_2
# callMe(b);
16: aload_1
17: invokestatic #4 // Method callMe:(LWithCasts$B;)V
# callMe((B) b);
20: aload_1
21: invokestatic #4 // Method callMe:(LWithCasts$B;)V
# callMe((B) a);
24: aload_2
25: checkcast #2 // class WithCasts$B
28: invokestatic #4 // Method callMe:(LWithCasts$B;)V
# implicit void return
31: return
}
We now have enough information to validate our hypothesis, callMe((B) b);
line 20-21 does not call the checkcast
operation4.
Hence, we can conclude that javac
remove useless cast during compilation.
Consequently, I can prevent the introduction of additional complexity in my compiler while keeping a clean and efficient bytecode.
Footnotes
-
But I will not discuss whether using explicit type casts is a good practice in this post. ↩
-
javap
documentation: https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javap.html. ↩ -
An introduction to bytecode: https://dzone.com/articles/introduction-to-java-bytecode. ↩
-
checkcast
documentation: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.checkcast ↩