8

This is a bit of a contrived example to get across the situation I'm encountering, but basically, I have an array of Objects that in reality contains arrays of integers, and I'm trying to cast it as such. Here is a code snippet that simulates this situation:

Object[] foo = new Object[3];
foo[0] = new int[] { 1, 2, 3 };
foo[1] = new int[] { 4, 5, 6 };
foo[2] = new int[] { 7, 8, 9 };
int[][] bar = (int[][]) foo;

When I try to do this, I get the exception Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [[I

Is there a way to do this without copying the array (which I may not want to do since foo will be very large in production)? If not, why not?

Catherine
  • 13,588
  • 9
  • 39
  • 60

3 Answers3

11

You can't change the intrinsic type of a Java object by casting it. That includes arrays. The object's type is fixed when the object is allocated, and cannot be changed.

So casting an Object[] to int[][] will never work.

In this case, while int[][] is a subtype of Object[], this is not sufficient for the typecast to be allowed at runtime. The actual rule in the JLS (5.5.3. Checked Casts at Run Time) is this:

"Here is the algorithm to check whether the run-time type R of an object is assignment compatible with the type T which is the erasure (§4.6) of the type named in the cast operator. If a run-time exception is thrown, it is a ClassCastException." ...

"If R is a class representing an array type RC[], that is, an array of components of type RC:"

  • "If T is an array type TC[], that is, an array of components of type TC, then a run-time exception is thrown unless one of the following is true:"

    • "TC and RC are the same primitive type."

    • "TC and RC are reference types and type RC can be cast to TC by a recursive application of these run-time rules for casting."

In this case TC is int[] and RC is Object and Object cannot be cast to int[]. Hence you get an exception.

To illustrate why this must happen, consider this (hypothetical) example:

   Object[] foo = new Object[3];
   foo[0] = new Object();
   int[][] bar = /* foo */
   int[] mystery = bar[0];

Assuming that it is possible to "cast" the value of foo, what should the mystery variable point to? It can't be an int[] because we didn't allocate one. It can't be an Object instance because that would break static typing.

In fact, the "cast" has to be illegal, or else Java's static type system falls apart.


The difference between your example and mine is that foo is not actually an array of arrays of integers, whereas in my case it is. Why can the VM not see the "actual" type of the object?

The point is that static typing is (primarily) enforced by the compiler not by the JVM. While the JVM could (in theory) figure out that the types are OK at runtime (and throw exceptions if they are not), the compiler cannot do this because it cannot determine in general what the types are going to be.


Unfortunately I'm not actually creating the array myself. I'm getting it from another function and the Object[] rather than int[][] is, as far as I can tell, the side effect of some serialization that I have no control over.

Unfortunately you have to either copy the array ... or use it as an Object[] and cast the elements to int[] to use them; e.g.

Object[] foo = new Object[3];
foo[0] = new int[] { 1, 2, 3 };
foo[1] = new int[] { 4, 5, 6 };
foo[2] = new int[] { 7, 8, 9 };
...
int nine = ((int[]) (foo[2]))[2];

There is no "trust me it is really an int[][]" option in Java.

If this a side-effect of serialization then the serialization is broken ... in some sense. Certainly, this won't happen with the standard Java Object Serialization protocol / implementation. That preserves the types of the objects ... unless you explicitly override them in the readObject / writeObject methods, etcetera.

Community
  • 1
  • 1
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Just trying to gain a firm understanding of this, obviously you're right about the way it functions--the difference between your example and mine is that foo is not actually an array of arrays of integers, whereas in my case it is. Why can the VM not see the "actual" type of the object? – Catherine Sep 17 '13 at 01:45
  • While correct, this does not solve the OP's problem. The solution is to access the `int[]`, as I humbly submit my answer does :-) – andy256 Sep 17 '13 at 01:45
  • @andy256 casting would definitely work, but I'm actually also really curious about WHY this does not work, so this is very useful. – Catherine Sep 17 '13 at 01:47
  • 2
    @andy256 - No. The *correct* answer is to do this: `Object[] foo = new int[3][];` ... and get rid of the type cast. – Stephen C Sep 17 '13 at 01:48
  • @StephenC unfortunately I'm not actually creating the array myself--I'm getting it from another function and the `Object[]` rather than `int[][]` is, as far as I can tell, the side effect of some serialization that I have no control over. – Catherine Sep 17 '13 at 01:54
  • @Catherine Why the aversion towards copying the array? `System.arraycopy()` is native and quite fast. – arshajii Sep 17 '13 at 01:55
  • @arshaji it's undesirable since there may be billions of entries in my top level array. It's not out of the question, I was just wondering if there's a way to avoid doing it (and at this point, I'd just really like to have a *firm* understanding what the reason is for not being able to cast an object to its "actual" type, which is why I'm still pursuing this line of questioning :)) – Catherine Sep 17 '13 at 01:58
  • @Catherine Sure, I understand. Note that arrays have a maximum length of ~2.1 billion. – arshajii Sep 17 '13 at 02:02
  • @StephenC Thanks, this has been extremely helpful. Is there any reading material on the topic that you could point me towards? – Catherine Sep 17 '13 at 02:09
  • 1
    @arshajii - You are correct about subtyping, but it turns out that the real reasoning for the failure (per the JLS) doesn't involve that at all. (In fact, subtyping explains why this is NOT a compile time error!) I have corrected my answer accordingly. – Stephen C Sep 17 '13 at 03:00
1

Since foo is array of objects it means it can contain anything, not only array of integers so compiler cant allow you to cast it to array of integers only because you know that it is inside.

If you want to use int[][] bar reference you will have to create separate int [3][] array first and copy content of foo to that array.

Pshemo
  • 122,468
  • 25
  • 185
  • 269
1

You need to cast each object as you access it.

    Object[] foo = new Object[3];
    foo[0] = new int[] { 1, 2, 3 };
    foo[1] = new int[] { 4, 5, 6 };
    foo[2] = new int[] { 7, 8, 9 };
    int[] foo2 = (int[])foo[2];
    System.out.println("foo[2,2]="+((int[])foo[2])[2]);

You should also wrap this in try ... catch, if you think the contents could be non-ints.

Edit: simplify code.

andy256
  • 2,821
  • 2
  • 13
  • 19