如何在Java中创建通用数组?

由于Java泛型的实现,你不能拥有这样的代码:

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

我如何在维护类型安全的同时实现这一点?

我在Java论坛上看到了这样一个解决方案:

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

但我真的不知道发生了什么事。


我必须回答一个问题:您的GenSet “已选中”还是“未选中”? 那是什么意思?

  • 选中 :强打字。 GenSet明确知道它包含什么类型的对象(即它的构造函数是用Class<E>参数显式调用的,当它们传递非E类型的参数时,方法会抛出异常。请参阅Collections.checkedCollection

    - >在这种情况下,你应该写:

    public class GenSet<E> {
    
        private E[] a;
    
        public GenSet(Class<E> c, int s) {
            // Use Array native method to create array
            // of a type only known at run time
            @SuppressWarnings("unchecked")
            final E[] a = (E[]) Array.newInstance(c, s);
            this.a = a;
        }
    
        E get(int i) {
            return a[i];
        }
    }
    
  • 未检查 :弱打字。 实际上对作为参数传递的任何对象都没有进行类型检查。

    - >在这种情况下,你应该写

    public class GenSet<E> {
    
        private Object[] a;
    
        public GenSet(int s) {
            a = new Object[s];
        }
    
        E get(int i) {
            @SuppressWarnings("unchecked")
            final E e = (E) a[i];
            return e;
        }
    }
    

    请注意,数组的组件类型应该是类型参数的擦除:

    public class GenSet<E extends Foo> { // E has an upper bound of Foo
    
        private Foo[] a; // E erases to Foo, so use Foo[]
    
        public GenSet(int s) {
            a = new Foo[s];
        }
    
        ...
    }
    
  • 所有这些都是Java中泛型的一个已知和故意的弱点:它使用擦除来实现,所以“泛型”类不知道它们在运行时创建的类型参数,因此不能提供类型 - 除非实施一些明确的机制(类型检查)。


    你总是可以这样做:

    E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];
    

    这是在Effective Java中实现泛型集合的建议方法之一; 第26项。没有类型错误,不需要重复投射数组。 但是,这会触发警告,因为它有潜在危险,应谨慎使用。 正如注释中所详述的,这个Object[]现在伪装成我们的E[]类型,如果使用不安全,可能会导致意外错误或ClassCastException

    根据经验,只要cast数组在内部使用(例如用于备份数据结构),此行为是安全的,并且不会返回或暴露给客户端代码。 如果您需要将一个泛型类型的数组返回给其他代码,那么您提到的反射Array类是正确的方法。


    值得一提的是,只要有可能,如果你使用泛型,你将会更快乐地使用List s而不是数组。 当然,有时你没有选择,但使用集合框架更加健壮。


    下面是如何使用泛型来获得一个精确类型的数组,同时保持类型安全性(与其他答案相反,这些答案会在编译时返回一个Object数组或者导致警告):

    import java.lang.reflect.Array;  
    
    public class GenSet<E> {  
        private E[] a;  
    
        public GenSet(Class<E[]> clazz, int length) {  
            a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));  
        }  
    
        public static void main(String[] args) {  
            GenSet<String> foo = new GenSet<String>(String[].class, 1);  
            String[] bar = foo.a;  
            foo.a[0] = "xyzzy";  
            String baz = foo.a[0];  
        }  
    }
    

    那编译没有警告,并且你可以看到main ,不管是什么类型的声明的实例GenSet作为,你可以指定a该类型的数组,你可以指定一个元素a该类型的变量,这意味着数组和数组中的值是正确的类型。

    它通过使用类文字作为运行时类型标记来工作,正如Java教程中所讨论的。 类文字被编译器视为java.lang.Class实例。 要使用它,只需使用.class来跟踪类的名称即可。 所以, String.class充当表示类StringClass对象。 这也适用于接口,枚举,任何维数组(例如String[].class ),基元(例如int.class )和关键字void (即void.class )。

    Class本身是泛型的(声明为Class<T> ,其中T代表Class对象所代表的类型),这意味着String.class的类型是Class<String>

    因此,无论何时调用GenSet的构造函数,都会为第一个参数传递一个类文字,以表示GenSet实例声明类型的数组(例如, String[].class for GenSet<String> )。 请注意,由于基元不能用于类型变量,因此您将无法获取原始数组。

    在构造函数内部,调用方法cast会将传递的Object参数cast转换为调用此方法的Class对象表示的Class 。 调用静态方法newInstancejava.lang.reflect.Array返回作为Object由所表示的类型的数组Class作为第一个参数,并通过指定的长度的传递的对象int作为第二个参数传递。 调用该方法getComponentType返回一个Class表示组件类型由所表示的所述阵列的对象Class的对象在其上调用的方法(例如String.classString[].classnull ,如果Class对象不表示一个数组) 。

    最后一句话并不完全准确。 调用String[].class.getComponentType()返回表示类StringClass对象,但其类型是Class<?> ,而不是Class<String> ,这就是为什么你不能做如下的事情。

    String foo = String[].class.getComponentType().cast("bar"); // won't compile
    

    返回Class对象的Class中的每个方法也是如此。

    关于Joachim Sauer对这个答案的评论(我自己没有足够的评价来评论),使用cast到T[]的例子会导致警告,因为在这种情况下编译器不能保证类型安全。


    编辑关于Ingo的评论:

    public static <T> T[] newArray(Class<T[]> type, int size) {
       return type.cast(Array.newInstance(type.getComponentType(), size));
    }
    
    链接地址: http://www.djcxy.com/p/8043.html

    上一篇: How to create a generic array in Java?

    下一篇: How can I pad an integers with zeros on the left?