Java 泛型数组解密
Java泛型由于只存在于编译时期,实际运行的类型还是Object类型,泛型数组也如下所示:
例如,我们试图定义一个泛型数组工具类,它可以用于按照顺序存入一个值,和指定位置取一个值,这实际上简易版的ArrayList,主要代码如下所示:
ackage genericArray;
/** * 类GenericArray.java的实现描述: * @author sweet 2012-6-17 下午7:23:28 */ public class GenericArray<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArray(int size){ this.array=(T[])new Object[size]; } public void put(int index,T data){ array[index]=data; } public T get(int index){ return array[index]; } /** * 这个方法会诱发 {@link java.lang.ClassCastException}<br/> * 表明无论如何数组的运行类型只能是Object[]类型 * * @return T[] */ public T[] rep(){ return array; } public static void main(String[] args){ GenericArray<Integer> genericArray=new GenericArray<Integer>(10); genericArray.put(0, 11); genericArray.put(1, 13); System.out.println(genericArray.get(0)); System.out.println(genericArray.get(2)); try{ Integer[] integerArray=genericArray.rep(); System.out.println(integerArray); }catch(Exception e){ System.out.println(e); } } }
这段代码有个问题,就是它在初始化泛型数组的时候,使用了强制转型,这会导致一个编译时期的警告,虽然我们可以使用@SuppressedWarnings("unchecked")来在编译期绕过检查,但不是一个好办法。
所以下面一段代码对它稍稍改善了下,直接定义Object[]数组,在插入值做向上转型(这个在运行期间可以自动完成),和在取出值时向下转型(这个需要手动强制转型,并且会有编译期警告,但是由于我们只保留了一个可以用于插入值的入口,所以这个问题看上去没那么严重)
/**
* 类GenericeArrayOptimization.java的实现描述:<br/> * 这个类试图利用Object[]去修复{@link GenericArray#rep()}方法所造成的泛型转换异常 * @author sweet 2012-6-17 下午7:44:07 * @see genericArray.GenericArray */ public class GenericeArrayOptimization<T> { private Object[] objectArray; public GenericeArrayOptimization(int size){ objectArray=new Object[size]; } /** * 这里只所以可以强转,是因为{@link GenericeArrayOptimization#put(int, Object)}<br/> * 方法放入的对象都是T类型 * * @param index * @return */ @SuppressWarnings("unchecked") public T get(int index){ return (T)objectArray[index]; } public void put(int index,T data){ objectArray[index]=data; } /** * 试图返回泛型数组的泛型<br/> * 调用该方法会抛出{@link java.lang.ClassCastException} * * @return */ @SuppressWarnings("unchecked") public T[] rep(){ return (T[])objectArray; } public static void main(String[] args){ GenericeArrayOptimization<Integer> array=new GenericeArrayOptimization<Integer>(10); array.put(0, 11); array.put(1, 13); System.out.println(array.get(1)); try{ //实践证明,这种转换方式还是会抛出类型转换错误 Integer[] t=array.rep(); }catch(Exception e){ System.out.println(e); } } }
从以上代码可以看出,没有任何方式可以推翻底层时候的数组类型,它在运行期间只能是Object类型,那么实际上有更好的办法,这个时候java.lang.reflect.Array.newInstance(Class<T> componentType,int length)就起到了作用。
我们先简单的看下JDK对这个方法的说明:
public static Object newInstance(Class<?> componentType,
int... dimensions) throws IllegalArgumentException, NegativeArraySizeException
创建一个具有指定的组件类型和维度的新数组。如果 componentType
表示一个非数组类或接口,则新数组具有 dimensions.length
维度,并且将 componentType
作为其组件类型。如果 componentType
表示一个数组类,则新数组的维数等于 dimensions.length
和 componentType
的维数的总和。在这种情况下,新数组的组件类型为 componentType
的组件类型。
新数组的维数不能超过该实现所支持的数组维数(通常为 255)。改造后的代码如下所示,用这个方法就可以创建我们真正需要的实际类型的数组。很可惜,在JDK类库里面,到处充斥着第二种方法的影子,不停的向上转型和向下转型。
/**
* 类GenerayArrayWithTypeToken.java的实现描述:<br/> * 由{@link java.lang.reflect.Array#newInstance(Class, int)}<br/> * 来实现泛型数组的返回,可参考{@link GenericeArrayOptimization#rep()} * @author sweet 2012-6-24 上午11:28:02 */ public class GenerayArrayWithTypeToken<T> { private T[] array; @SuppressWarnings("unchecked") public GenerayArrayWithTypeToken(Class<T> componentType,int length){ array=(T[])Array.newInstance(componentType, length); } public void put(int index,T data){ this.array[index]=data; } public T get(int index){ return this.array[index]; } public T[] rep(){ return this.array; } public static void main(String[] args){ GenerayArrayWithTypeToken<Integer> genericArray=new GenerayArrayWithTypeToken<Integer>(Integer.class,10); genericArray.put(0, 11); genericArray.put(1, 31); System.out.println(Arrays.toString(genericArray.rep())); } }