问题
用Java写一个类似Python中random.choice 功能的方法时碰到了一个令我费解的情况。
源代码如下:
RandomUtil.java
import java.util.ArrayList; import java.util.Arrays; import java.util.Random; public class RandomUtil { public static final Random random = new Random(); public static <T> T[] choice(T[] arr, int n) { List<T> result = new ArrayList<T>(); while(result.size() < n) { int i = random.nextInt(n); if(!result.contains(arr[i])) { result.add(arr[i]); } } return (T[]) result.toArray(); } }
Test.java
import java.util.Arrays; public class Test { public static void main(String[] args) { Integer[] array = {1, 2, 3, 4}; Integer[] res = RandomUtil.choice(array, 2); System.out.println(Arrays.toString(res)); } }
看上去好像没什么问题,编译通过,除了提示有未经检验的类型转换。然而运行的时候抛出了ClassCastException 异常。关键是,异常居然指向Test.java第七行:
RandomUtil.choice 方法使用了泛型,接受一个任意类型的数组,然后从数组中随机抽出一定个数的元素,添加到相同类型的List中,最后将List转换成数组返回。
main函数中调用RandomUtil.choice 时传入了Integer 类型的数组,理论上T就代表Integer 类型,result 为List<Integer> ,返回时调用ArrayList.toArray() 方法返回Object 数组,然后强转为Integer[] 最后返回。
也就是说,RandomUtil.java的20行将Object[] 数组强制转化成Integer[] 成功并返回了,但到了把返回值赋值给同样时Integer[] 类型的变量时就不认账了。。。根据报错信息,返回值又变成了Object[] 。真是摸不着头。。。
PS: JDK1.6和JDK1.8均是如此。
打脸
简而言之,以下两点造成以上问题:
- 方法ArrayList.toArray() 返回Object[] 类型变量
- Java泛型擦除
无参数的ArrayList.toArray() 方法返回的是Object[] ,根本不能强制转换成String[] 。
之所以在20行没有抛出异常,是因为运行时泛型是擦除的。20行实际上什么也没做,运行时JVM并不知道T这个泛型参数是什么类型,而是以Object 替代,所以20行类型转换没有意义。而到了赋值到Integer[] 类型的变量上时,才发现了类型不匹配的问题,从而抛出异常。
修改
public static <T> T[] choice(T[] arr, int n) { Random random = new Random(); List<T> result = new ArrayList<T>(); while(result.size() < n) { int i = random.nextInt(n); if(!result.contains(arr[i])) { result.add(arr[i]); } } Object[] target = (Object[]) Array.newInstance(arr.getClass().getComponentType(), n); return (T[]) result.toArray(target); }
相关文章:
https://stackoverflow.com/questions/36161096/generics-and-classcastexception
https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html
近期评论