A Bug in Java's Type System
I recently came across an interesting Java interview question and wanted to share it here. The code is as follows:
1 | String[] a = new String[2]; |
The question: what’s wrong with these 4 lines of code?
When I first saw this, my gut reaction was: line 2 is the problem.
In Java, arrays have no inheritance relationship.
String[]is represented in the JVM as[Ljava/lang/String;, andObject[]as[Ljava/lang/Object;. Their common superclass isjava.lang.Object.
Java’s Behavior
When I typed the code into my IDE, there were no syntax errors. I thought maybe it was an IDE bug, so I opened VIM and wrote a test class:
1 | public class Main { |
Manual javac compilation succeeded – interesting. When I decompiled the class file, it looked like this:
1 | public static void main(String[] var0) { |
To verify the decompilation, I used javap to dump the bytecode for the main(String[]) method:
1 | 0: iconst_2 |
It appears javac optimized main(String[]) by eliminating the local variable Object[] b. Can you guess the runtime result?
You might think ClassCastException – but where’s the checkcast instruction in the bytecode?
Let’s run it:
1 | Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer |
There’s actually an ArrayStoreException! Let’s look at its source:
1 | /** |
The Javadoc makes it clear. But if they knew this was problematic, why allow it? Like ArithmeticException – if an exception might occur, why doesn’t the IDE warn you? More fundamentally, why is assigning String[] to Object[] considered valid?
With these questions in mind, I dug into the JVM specification. The aastore instruction provides a clear definition:
1 | aastore value, index, arrayref |
At runtime, the value type must be compatible with the array component type referenced by arrayref. Specifically, assigning a reference type S (source) to an array component of reference type T (target) is only permitted when:
- If
Sis a Class type:- If
Tis a Class type,Smust be the same class asT, orSmust be a subclass ofT; - If
Tis an interface type,Smust implement interfaceT.
- If
- If
Sis an interface type:- If
Tis a Class type,Tmust beObject. - If
Tis an interface type,Tmust be the same interface asSor a superinterface ofS.
- If
- If
Sis an array typeSC[](i.e., with component typeSC):- If
Tis a Class type,Tmust beObject. - If
Tis an interface type,Tmust be one of the interfaces implemented by arrays (JLS 4.10.3). - If
Tis an array typeTC[](i.e., with component typeTC), one of the following must be true:TCandSCare the same primitive type.TCandSCare reference types, and typeSCis assignable toTC.
- If
So, according to the last rule, String[] can be assigned to Object[] because:
String[]andObject[]are both reference typesStringis assignable toObject
Given that, what would the following code produce?
1 | public static void main(String[] args) { |
The output:
1 | class java.lang.Object |
This tells us:
- Arrays have no inheritance relationship
- Two different reference types with no inheritance relationship can be assigned to each other (doesn’t this contradict the definition of polymorphism?)
Kotlin’s Behavior
What if we translate the code above into Kotlin – would the result be any different?
- Blog Link: https://johnsonlee.io/2020/05/12/a-bug-of-java-type-system.en/
- Copyright Declaration: 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
