如何在Java中创建类型安全的通用数组?

4

我希望在Java中创建一个通用数组,保持通常由Java提供的类型安全性。

我正在使用以下代码:

class Stack<T> { 

private T[] array = null;
public Stack(Class<T> tClass, int size) {
   maximumSize = size;
   // type unsafe
   //array = (T[])new Object[maximumSize];

   this.array = (T[])java.lang.reflect.Array.newInstance(tClass,maximumSize);
}

这段代码是否是类型安全的?如果是,为什么需要强制转换(cast)?


在上面的代码中,(T[])只是将其转换为Object[],因为类型T在编译时是未知的。因此,array必须是Object[]类型(或者简单地说是Object)。如果您返回一个类型为T[]的值,则在调用点处,当分配或使用方法的返回值时,编译器将抛出一个隐藏的转换到适当类型的转换。 - Hot Licks
你能更新你的代码使其编译吗?T 是从哪里来的?array 声明在哪里?例如,这是一个静态方法(它仅是 Array#newInstance 的包装器),还是 array 是一个字段,并且 T 是类的类型参数? - Radiodef
4个回答

9
Array.newInstance(...)方法的返回类型为Object。因此,您不能直接将其分配给除Object之外的任何东西。因此,您需要进行转换。
该方法委托给一个本地方法,该方法会创建指定组件类型和长度的新数组。因此,它正在创建类型为T的数组。
类型安全性,假设array被声明为:
T[] array;

使用Class<T>参数和相同类型变量的强制转换可以保证

@SuppressWarnings("unchecked")

在您的源代码中,使用注释解释上述原因。始终在抑制警告的转换时注释说明其安全性。

为什么我会收到未经检查的警告? - Giuseppe Pes
1
您正在进行一个 未经检查的转换,因此会收到使用它的警告。 - Marko Topolnik
1
请注意,如果有一个专门用于引用类型数组的newInstance,编译器本可以保证它。 static <T> T[] newInstance(Class<T> c, int size) - Marko Topolnik
5
请注意,这样做会对基本类型抛出异常。例如,int.classClass<Integer>,因此将int[]强制转换为Integer[]可能会发生异常。无法通用地表示基本类型,这就是为什么它可能不安全的原因。 - Radiodef
基本上,如果按照您目前的方式进行泛型操作,无法包含原始类型。@GiuseppePes - Radiodef
显示剩余4条评论

4
由于原始的类对象,它并不是类型安全的。例如,我可以通过以下方式创建一个新的堆栈:
new Stack<Boolean>(boolean.class, 10);

编译器不会出错,但程序会抛出异常,因为boolean.class 是一个Class<Boolean>,而boolean[] 不能被强制转换为Boolean[]
你展示的替代方法已经被注释掉了。
array = (T[])new Object[size];

实际上,Java某种程度上是类型安全的,但原因不同:这是由于擦除。例如,您不能将new Object[size]转换为Number[],但转换从未在数组上发生。它发生在稍后某个时刻,例如当您从方法中返回数组的元素时(此时元素被强制转换)。如果您试图将数组返回到对象外部,它将抛出异常。

通常的解决方案不是对数组进行泛型编写。而是像这样做:

class Stack<E> {
    Object[] array = new Object[10];
    int top;

    void push(E elem) {
        if(top == array.length)
            array = Arrays.copyOf(array, array.length * 2);

        array[top++] = elem;
    }

    E pop() {
        @SuppressWarnings("unchecked")
        E elem = (E)array[--top]; // type safe cast

        array[top] = null;

        return elem;
    }
}

上面的代码是类型安全的,因为只能将 E 推入数组中。JDK Stack(它扩展了 Vector)和 ArrayList 都是这样工作的。

如果要使用 newInstance,则必须拒绝原始类型,因为没有办法以通用方式表示它们:

Stack(Class<T> tClass, int size) {
    if(tClass.isPrimitive())
        throw new IllegalArgumentException();

    // ...
}

1
很详细的答案! - javaguy

1

Array.newInstance返回一个Object,因此需要进行转换。在这种情况下,编译器总是会发出警告。这是泛型的限制。


0

我们知道泛型是在编译时检查的,所以我的朋友, java.lang.reflect.Array.newInstance(tClass, size); 返回给你对象,你需要进行类型转换。如果数组不是T[]类型,则可能会出现编译时错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接