I recently came across an interesting Java interview question and wanted to share it here. The code is as follows:

1
2
3
4
String[] a = new String[2];
Object[] b = a;
a[0] = "hello";
b[1] = Integer.valueOf(0);

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;, and Object[] as [Ljava/lang/Object;. Their common superclass is java.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
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
String[] a = new String[2];
Object[] b = a;
a[0] = "hello";
b[1] = Integer.valueOf(0);
System.out.println(a);
}
}

Manual javac compilation succeeded – interesting. When I decompiled the class file, it looked like this:

1
2
3
4
public static void main(String[] var0) {
String[] var1 = new String[]{"hello", 0};
System.out.println(var1);
}

To verify the decompilation, I used javap to dump the bytecode for the main(String[]) method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 0: iconst_2
1: anewarray #2 // class java/lang/String
4: astore_1
5: aload_1
6: astore_2
7: aload_1
8: iconst_0
9: ldc #3 // String hello
11: aastore
12: aload_2
13: iconst_1
14: iconst_0
15: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: aastore
19: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
22: aload_1
23: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
26: return

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
2
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
at Main.main(Main.java:6)

There’s actually an ArrayStoreException! Let’s look at its source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Thrown to indicate that an attempt has been made to store the
* wrong type of object into an array of objects. For example, the
* following code generates an <code>ArrayStoreException</code>:
* <blockquote><pre>
* Object x[] = new String[3];
* x[0] = new Integer(0);
* </pre></blockquote>
*
* @author unascribed
* @since JDK1.0
*/
public
class ArrayStoreException extends RuntimeException {
private static final long serialVersionUID = -4522193890499838241L;

/**
* Constructs an <code>ArrayStoreException</code> with no detail message.
*/
public ArrayStoreException() {
super();
}

/**
* Constructs an <code>ArrayStoreException</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public ArrayStoreException(String s) {
super(s);
}
}

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:

  1. If S is a Class type:
    • If T is a Class type, S must be the same class as T, or S must be a subclass of T;
    • If T is an interface type, S must implement interface T.
  2. If S is an interface type:
    • If T is a Class type, T must be Object.
    • If T is an interface type, T must be the same interface as S or a superinterface of S.
  3. If S is an array type SC[] (i.e., with component type SC):
    • If T is a Class type, T must be Object.
    • If T is an interface type, T must be one of the interfaces implemented by arrays (JLS 4.10.3).
    • If T is an array type TC[] (i.e., with component type TC), one of the following must be true:
      • TC and SC are the same primitive type.
      • TC and SC are reference types, and type SC is assignable to TC.

So, according to the last rule, String[] can be assigned to Object[] because:

  1. String[] and Object[] are both reference types
  2. String is assignable to Object

Given that, what would the following code produce?

1
2
3
4
5
6
7
public static void main(String[] args) {
String[] a = new String[2];
Object[] b = a;
System.out.println(String[].class.getSuperclass());
System.out.println(Object[].class.getSuperclass());
System.out.println(Object[].class.isAssignableFrom(String[].class));
}

The output:

1
2
3
class java.lang.Object
class java.lang.Object
true

This tells us:

  1. Arrays have no inheritance relationship
  2. 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?