Converting a Collection to an array

   原文: http://www.javablogging.com/converting-a-collection-to-an-array/

   如果你已经使用了java5的Collections有可能被Collection<T>输出数组类型为T的问题难倒。在Collection Interface中有方法 toArray() 返回 Object[] 类型。但是,如果java5的Collection使用泛型,难倒不应该得到一个array T[] 使用在我们的Collection<T>的声明中?当然有一个方法 T[] toArray(T[] a)。但是哪来的这个奇怪的“T[] a”?为什么不是一个简单的没有奇怪参数的方法 T[] toArray(T[] a)?看起来在java5中实现这样一个方法不应该有大的问题,但是事实上不肯能做到在java5,我们在下面说明为什么。

    我们从尝试自己实现这样一个方法开始,例如,我们可以通过继承 ArrayList 类来实现方法T[] toArrayT()。为了简单我们通过强制转换 Object[] toArray() 方法为T[]来实现:



import java.util.*;

/**
 * Implementation of the List interface with "T[] toArrayT()" method.
 * In a normal situation you should never extend ArrayList like this !
 * We are doing it here only for the sake of simplicity.
 * @param <T> Type of stored data
 */
class HasToArrayT<T> extends ArrayList<T> {
    public T[] toArrayT() {
        return (T[]) toArray();
    }
}

public class Main {
    public static void main(String[] args) {
        // We create an instance of our class and add an Integer
        HasToArrayT<Integer> list = new HasToArrayT<Integer>();
        list.add(new Integer(4));

        // We have no problems using the Object[] toArray method
        Object[] array = list.toArray();
        System.out.println(array[0]);

        // Here we try to use our new method... but fail on runtime
        Integer[] arrayT = list.toArrayT(); // Exception is thrown :
        // "Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer"
        System.out.println(arrayT[0]);
    }
}


   如果你编译这段代码,当我们尝试使用这个新方法它会抛出一个 exception。但是为什么呢?但是为什么?难倒是我们做的不对?存储是Integers 和 toArray 方法返回的Objects数组,所以这才是问题所在。

    Java 编译器 给我们一个提示来理解我们的错误。通过编译你将看到第11行的warning,说一些什么“Type Safety”。 Unchecked cast 从 Object[] 到 T[],跟随这个问题检查这个 array 从方法 toArrayT() 返回的真正类型是 Integer[],把下面的代码放在main()中代码
Integer[] arrayT = list.toArrayT()
之前:

    System.out.println("toArrayT() returns Integer[] : "
    + (list.toArrayT() instanceof Integer[]));


    当你运行这个程序,你应该得到一个"false",这意味着toArrayT()返回的 并不是Integer[],为什么呢?也许许多人知道答案,这就是 Type Erasure。基本上,编译器在编译过程中会去除一切关于泛型的任何类型。当程序运行时,所有的 T 在 HasToArrayT类 中的表象并不是一个简单的对象。这意味着,方法toArrayT 并没有转换成Integer[],即使泛型中定义的为Integer。其实这里还有第二个问题,来自java处理数组转换的方式。现在我们只想集中在第一个问题--泛型的使用。

    所以,如果 T 代表 Integer 却会在运行期擦除,有没有什么方法返回真正的array of type Integer[]?当然可以而且是已经存在的,方法 T[] toArray(T[] a)在Collections框架就是这个作用。但是代价就是添加了一个自己创建的参数实例。怎么使用它呢?让我们看看它们是怎么实现的?例如,ArrayList的实现:

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}


    按照javadoc中的这个方法的解释,如果这个制定的数组 a 有足够大的空间满足elements放在这个数组中,如果没有足够空间将会创建一个新的数组。我们看到实现中它通过Arrays.copyOf 方法创建一个新的数组,通过 a.getClass() 来指定要创建数组的类型。 这就是为什么它需要一个参数--为了知道要创建数组的类型。然后你也许会说通过 T.class 替代 a.getClass()会不会显得更简单?不!因为type erasure,T不是一个我们指定的一个类型。而且我们如果我们使用T.class会得到一个编译器错误!也许你想到一个创建新数组的简单方法 -- new T[], 但是由于同样的原因仍然不可能。

    直接的说,为了返回一个真正指定类型的array of T,必须为函数 toArray 提供相应的类型。现在并没有其他的方式可以提供函数中创建的数组类型,由于泛型 T 在Collection运行期创建时被取消。

    另外一个途径就是通过传递一个 Class<T[]> 输入参数实现这个函数,这种方式通过创建一个新的数组T[]替代 toArray(a) 中的 a, 我们可以用 .class 域 -- toArrayT(TYPE[].class)

public T[] toArrayT(Class<T[]> c) {
    return (T[]) Arrays.copyOf(toArray(), size(), c);
}


    它仍然需要一个参数,但是这有两个好处在T[] toArray(T[] a)函数。首先,它不需要另外常见一个数组而是直接使用传递来的实例。第二,它简单,当然它的确定也是有一个传入参数。

    总之,没有完美的解决办法,最好的解决办法是不使用数组。数组代码很难控制并且导致人去犯错并出现在运行的时候。如果你经常使用collections 并且熟悉,你想不能逃避关于注意 toArray 方法。
   

你可能感兴趣的:(框架,sun)