先来看看Arrays.copyOf方法的源码
@IntrinsicCandidate
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
可以说是相当匪夷所思。
第一个点就是为什么newType==Object[].class为什么要强转一下。因为newType这里被捕获了,所以这里newType的值是Class<X1[]>这里X1表示某种未知类型,所以尽管Object[]是X1[]的父类,但是Class<Object[]>并不是Class<X1[]>的父类,在stackOverFlow上有一篇这样的问题,回答也挺完整的。所以这里把两个值都转成了Object类型。上面那个回答里给出的另一种强转方式:
(newType == (Class<? extends Object[]>)Object[].class)
也是类似的理解,首先newType是Class<X1[]>类型,Object[].class显然是Class<Object[]>类型,所以是Class<? extends Object[]>的子类型(注意这里是通配符不是捕获之后的类型,Class<X1[]>表示的是某种未知类型,而Class<? extends T[]>是个单独的类型,两者是不同的东西,前者是后者的子类型),所以这个强转没问题,然后因为newType类型Class<X1[]>也是Class<? extends Object[]>的子类型,所以两者可以比较。
那究竟什么时候会发生捕获呢,这里讲的很清楚,我看下来大概意思就是:只有纯粹作为赋值表达式左值的时候不会发生捕获,而要求值或者调用方法等其他情况都会发生捕获,看下面这个例子,这里出错的行都标出来了
public static void test(ArrayList<? extends B> list) {
//继承关系C继承B继承A
list = new ArrayList<A>(); //这是第38行
list = new ArrayList<B>();
list = new ArrayList<C>();
ArrayList<A> newListA = (ArrayList<A>) list; //这是第42行
ArrayList<B> newListB = (ArrayList<B>) list;
ArrayList<C> newListC = (ArrayList<C>) list;
var res = list.get(0);
list.set(0, list.get(0)); //这是第47行
}
打开命令行用javac编译一下,或者直接放在IDEA里面运行,会得到这样的结果
IDEA里是这样的
其实还是挺清楚地,有CAP标志的就是捕获了,? extends ...的就是未捕获的。很明显,list为赋值表达式左值时还是? extends ...类型,未捕获,为右值时就是捕获了,命令行显示的是<CAP#1>,46行,47行调用方法时其实都捕获了,只不过这里标注的不是很清楚,打开-Xdiags:verbose就很清楚了(javac Hello.java -Xdiags:verbose),是这样的
显然确实都是捕获了,至于47行的报错,原因是这是个? extends ...是个上限通配符,所以不接受任何参数,不过作为返回值是可以的,可以看这篇文章,JAVA核心卷一的泛型哪一章最末尾也稍微讲了一些,不过并不是那么好理解。
另外值得一提的一点就是,泛型的强制类型转换,这一点其实上面那第二个错误已经很清楚了,首先你如果要对泛型参数进行强制类型转换,那肯定是会发生捕获,因为赋值表达式左边不会有强制类型转换,比如上面(ArrayList<A>) list就会报错,因为此时list的类型是ArrayList<X1>是发生了捕获的。那ArrayList<X1>到(ArrayList<A>)会不会报错呢,当然会,因为X1是B的子类型,而A是B的父类型,所以肯定转不了,记住ArrayList<A>和ArrayList<B>和ArrayList<C>之间并没有继承关系。那转到ArrayList<B>和ArrayList<C>为啥不报错呢?因为这是有可能成功的,比如你传进来的刚好就是一个ArrayList<B>类型的,这当然可以,因为参数只要求是ArrayList<? extends B>,那么它就可以转成ArrayList<B>(自己转自己嘛)。同理也可能刚好传进来一个ArrayList<C>。但是不管你怎么传,ArrayList<A>肯定是转不了的,所以会报错。这就不是编译器能判断的了,只有运行的时候才判断的了,所以这里会有个非检查型错误。
至于为什么要判断它是不是Object[],当然也是为了优化效率,Stack Overflow那篇答主也讲得很清楚,因为反射操作效率不高,这里讲了(stackoverflow那篇文章的答主说的,我并没有看)
Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
/***
*_______________#########_______________________
*______________############_____________________
*______________#############____________________
*_____________##__###########___________________
*____________###__######_#####__________________
*____________###_#######___####_________________
*___________###__##########_####________________
*__________####__###########_####_______________
*________#####___###########__#####_____________
*_______######___###_########___#####___________
*_______#####___###___########___######_________
*______######___###__###########___######_______
*_____######___####_##############__######______
*____#######__#####################_#######_____
*____#######__##############################____
*___#######__######_#################_#######___
*___#######__######_######_#########___######___
*___#######____##__######___######_____######___
*___#######________######____#####_____#####____
*____######________#####_____#####_____####_____
*_____#####________####______#####_____###______
*______#####______;###________###______#________
*________##_______####________####______________
*/