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?
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 letT
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 ofT
with respect to all synthetic type variables mentioned byT
(§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 ofT
.
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
.