Search code examples
javaarraysobjectcastingprimitive

Java: Casting Object to primitive array using Class.cast()


Casting a known primitive array masquerading as an Object back to it's original primitive array type using the Class.cast method involves breaking the operation up into two assignments first before it's use can be correctly compiled.


import java.util.Arrays;

class Scratch {
    public static void main(String[] args) {
        Object src = new int[]{1,2,3}; 
        castAndSet(src);
    }

    // assign index i of src to dest after casting src to int[]
    public static void castAndSet(Object src){ 
        int[] dst = new int[]{4,5,6};
        int i = 0; // assume array length greater than zero.
        if(dst.getClass().equals(src.getClass())){ // assert both are same class
            // src should also be an int[], so no ClassCastException to worry about

            dst[i] = ((int[])src)[i];  //this works of course
            // but can we cast and set using Class#cast method?

            // shouldn't we be able to do this?
            dst[i] = dst.getClass().cast(src)[i]; // doesn't compile
            dst[i] = (dst.getClass()).cast(src)[i]; // doesn't compile
            dst[i] = ((dst.getClass()).cast(src))[i]; // doesn't compile

            // let's break it apart
            var dstClass = dst.getClass(); // dstClass is Class<? extends int[]>
                                           // as per getClass javadocs.
            dst[i] = dstClass.cast(src)[i]; // still doesn't compile
                                            // array type expected;found capture<? extends int[]>

            // instead we have to break it apart twice
            var dstClass2 = dst.getClass(); // dstClass is Class<? extends int[]>
            var src2 = dstClass2.cast(src); // src2 is int[], magic
            dst[i] = src2[i]; // compiles.

            System.out.println(Arrays.toString(dst));
        }
        else throw new RuntimeException("objects are not of the same array class");
    }
}

Parenthesized operations seems like this cast and set should be a one liner but there is some implicit conversion going on during assignment to a variable. The 'var' keyword is used and no where is an explicit type hinted at to guide this implicit conversion from what I can tell.

What is going on?


Solution

  • The return type of dst.getClass is Class<? extends int[]>, instead of Class<int[]> which you might have expected. See here if you don't understand why.

    The return type of Class<T>.cast is T, but what if T is ? extends int[] like in your code? Capture conversion applies, and Class<? extends int[]> becomes a Class<CAPTURE>, where CAPTURE is a fresh type variable, with int[] as its upper bound. The idea is that the compiler doesn't know anything about ? extends int[], except that it is a subtype of int[]. Of course, we as humans know that int[] does not have any subtypes except itself.

    In any case, the return type of cast is CAPTURE. CAPTURE is a type variable, not an array type, so you cannot use array indexing [i] on it.

    You are able to do:

    var src2 = dst.getClass().cast(src);
    dst[i] = src2[i];
    

    because although dst.getClass().cast(src) is of type CAPTURE, type projection applies when you assign that to a var. From the language specification:

    If the LocalVariableType is var, then let T be the type of the initializer expression when treated as if it did not appear in an assignment context, and were thus a standalone expression. The type of the local variable is the upward projection of T with respect to all synthetic type variables mentioned by T (§4.10.5).

    The upward projection of a type T with respect to a set of restricted type variables is defined as follows:

    ...

    • If T is a restricted type variable, then the result is the upward projection of the upper bound of T.

    So CAPTURE here gets projected to its upper bound int[].

    Of course, int[] src2 = dst.getClass().cast(src); also compiles because CAPTURE has an upper bound of int[], so int[] is a supertype of CAPTURE.