首页 文章

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

提问于
浏览
931

由于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;
}

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

29 回答

  • 609

    这个解决方案怎么样?

    @SafeVarargs
    public static <T> T[] toGenericArray(T ... elems) {
        return elems;
    }
    

    它的工作原理看起来太简单了 . 有什么缺点吗?

  • 169

    试试这个 .

    private int m = 0;
    private int n = 0;
    private Element<T>[][] elements = null;
    
    public MatrixData(int m, int n)
    {
        this.m = m;
        this.n = n;
    
        this.elements = new Element[m][n];
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                this.elements[i][j] = new Element<T>();
            }
        }
    }
    
  • 58
    private E a[];
    private int size;
    
    public GenSet(int elem)
    {
        size = elem;
        a = (E[]) new E[size];
    }
    
  • 0

    你可以使用一个演员:

    public class GenSet<Item> {
        private Item[] a;
    
        public GenSet(int s) {
            a = (Item[]) new Object[s];
        }
    }
    
  • 1

    我不得不问一个问题:你的 GenSet "checked"或"unchecked"?那是什么意思?

    • Checked :强打字 . GenSet 明确知道它包含的对象类型(即它的构造函数是使用 Class<E> 参数显式调用的,并且当方法传递非_1177416_类型的参数时,方法将抛出异常 . 请参阅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];
        }
    }
    
    • Unchecked :弱打字 . 实际上没有对作为参数传递的任何对象进行类型检查 .

    • 在那种情况下,你应该写

    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;
        }
    }
    

    请注意,数组的组件类型应为type参数的erasure

    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中已知且有意识的泛型弱点:它是使用擦除实现的,因此“泛型”类不知道它们在运行时创建的类型参数,因此无法提供类型 - 安全,除非实施一些显式机制(类型检查) .

  • 4

    要扩展到更多维度,只需将 [] 和维度参数添加到 newInstance()T 是类型参数, clsClass<T>d1d5 是整数):

    T[] array = (T[])Array.newInstance(cls, d1);
    T[][] array = (T[][])Array.newInstance(cls, d1, d2);
    T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
    T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
    T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);
    

    有关详细信息,请参阅Array.newInstance() .

  • 0

    我找到了一种适合我的快捷方式 . 请注意,我只在Java JDK 8上使用过它 . 我不知道它是否适用于以前的版本 .

    虽然我们无法实例化特定类型参数的泛型数组,但我们可以将已创建的数组传递给泛型类构造函数 .

    class GenArray <T> {
        private T theArray[]; // reference array
    
        // ...
    
        GenArray(T[] arr) {
            theArray = arr;
        }
    
        // Do whatever with the array...
    }
    

    现在在main中我们可以像这样创建数组:

    class GenArrayDemo {
        public static void main(String[] args) {
            int size = 10; // array size
            // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
            Character[] ar = new Character[size];
    
            GenArray<Character> = new Character<>(ar); // create the generic Array
    
            // ...
    
        }
    }
    

    为了更灵活地使用数组,您可以使用链接列表,例如 . ArrayList和Java.util.ArrayList类中的其他方法 .

  • 5

    我想知道这段代码是否会创建一个有效的通用数组?

    public T [] createArray(int desiredSize){
        ArrayList<T> builder = new ArrayList<T>();
        for(int x=0;x<desiredSize;x++){
            builder.add(null);
        }
        return builder.toArray(zeroArray());
    }
    
    //zeroArray should, in theory, create a zero-sized array of T
    //when it is not given any parameters.
    
    private T [] zeroArray(T... i){
        return i;
    }
    

    编辑:也许创建这样一个数组的另一种方法,如果你需要的大小已知且很小,那么只需将所需数量的“null”提供给zeroArray命令?

    虽然这显然不如使用createArray代码那样通用 .

  • 2

    这是唯一类型安全的答案

    E[] a;
    
    a = newArray(size);
    
    @SafeVarargs
    static <E> E[] newArray(int length, E... array)
    {
        return Arrays.copyOf(array, length);
    }
    
  • 28

    一个简单但易混乱的解决方法是在主类中嵌套第二个“holder”类,并使用它来保存数据 .

    public class Whatever<Thing>{
        private class Holder<OtherThing>{
            OtherThing thing;
        }
        public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
    }
    
  • 3

    我实际上找到了一个非常独特的解决方案来绕过无法启动通用数组 . 你要做的是创建一个类,它接受泛型变量T,如下所示:

    class GenericInvoker <T> {
        T variable;
        public GenericInvoker(T variable){
            this.variable = variable;
        }
    }
    

    然后在你的数组类中,让它像这样开始:

    GenericInvoker<T>[] array;
    public MyArray(){
        array = new GenericInvoker[];
    }
    

    启动 new Generic Invoker[] 将导致未选中的问题,但实际上不应该有任何问题 .

    要从数组中获取,您应该像这样调用数组[i] .variable:

    public T get(int index){
        return array[index].variable;
    }
    

    其余的,比如调整数组的大小可以使用Arrays.copyOf()来完成,如下所示:

    public void resize(int newSize){
        array = Arrays.copyOf(array, newSize);
    }
    

    添加功能可以像这样添加:

    public boolean add(T element){
        // the variable size below is equal to how many times the add function has been called 
        // and is used to keep track of where to put the next variable in the array
        arrays[size] = new GenericInvoker(element);
        size++;
    }
    
  • -1

    嗨虽然线程已经死了,但我想提请你注意这个:

    泛型用于在编译期间进行类型检查:

    • 因此,目的是检查您所需要的是什么 .

    • 您返回的是消费者需要的东西 .

    • 检查一下:

    enter image description here

    在编写泛型类时,不要担心类型转换警告 . 在使用它时担心 .

  • 0

    java中不允许使用通用数组创建,但您可以这样做

    class Stack<T> {
    private final T[] array;
    public Stack(int capacity) {
        array = (T[]) new Object[capacity];
     }
    }
    
  • -1

    其他人建议的强迫演员对我不起作用,除了非法演员之外 .

    但是,这个隐式转换工作正常:

    Item<K>[] array = new Item[SIZE];
    

    其中Item是我定义的包含成员的类:

    private K value;
    

    这样,您将获得类型为K的数组(如果项目仅具有值)或您希望在类Item中定义的任何泛型类型 .

  • 0

    在Java 8中,我们可以使用lambda或方法引用来创建一种通用数组 . 这类似于反射方法(传递 Class ),但这里我们没有使用反射 .

    @FunctionalInterface
    interface ArraySupplier<E> {
        E[] get(int length);
    }
    
    class GenericSet<E> {
        private final ArraySupplier<E> supplier;
        private E[] array;
    
        GenericSet(ArraySupplier<E> supplier) {
            this.supplier = supplier;
            this.array    = supplier.get(10);
        }
    
        public static void main(String[] args) {
            GenericSet<String> ofString =
                new GenericSet<>(String[]::new);
            GenericSet<Double> ofDouble =
                new GenericSet<>(Double[]::new);
        }
    }
    

    例如,<A> A[] Stream.toArray(IntFunction<A[]>)使用它 .

    这也可以使用匿名类在Java 8之前完成,但它更麻烦 .

  • 1

    下面是如何使用泛型来获得一个正在寻找的类型的数组,同时保留类型安全性(与其他答案相反,它将返回一个 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 Tutorials中所述 . 编译器将类文字视为 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 表示 GenSet<String> ) . 请注意,您赢得了't be able to get an array of primitives, since primitives can'用于类型变量 .

    在构造函数内部,调用方法 cast 将返回传递的 Object 参数强制转换为由调用该方法的 Class 对象表示的类 . 在 java.lang.reflect.Array 中调用静态方法 newInstance 将返回 Object ,该数组由 Class 对象表示,该对象作为第一个参数传递,并且 int 指定的长度作为第二个参数传递 . 调用方法 getComponentType 会返回一个 Class 对象,该对象表示由调用该方法的 Class 对象表示的数组的组件类型(例如 String.class 表示 String[].class ,如果 Class 对象不表示数组,则为 null ) .

    最后一句话并不完全准确 . 调用 String[].class.getComponentType() 会返回表示类 StringClass 对象,但其类型为 Class<?> ,而不是 Class<String> ,这就是为什么您不能执行以下操作的原因 .

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

    同样适用于 Class 中返回 Class 对象的每个方法 .

    关于Joachim Sauer对this answer的评论(我自己没有足够的声誉对其进行评论),使用强制转换为 T[] 的示例将导致警告,因为在这种情况下编译器无法保证类型安全 .


    关于Ingo的评论编辑:

    public static <T> T[] newArray(Class<T[]> type, int size) {
       return type.cast(Array.newInstance(type.getComponentType(), size));
    }
    
  • 36

    另请参阅此代码:

    public static <T> T[] toArray(final List<T> obj) {
        if (obj == null || obj.isEmpty()) {
            return null;
        }
        final T t = obj.get(0);
        final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
        for (int i = 0; i < obj.size(); i++) {
            res[i] = obj.get(i);
        }
        return res;
    }
    

    它将任何类型对象的列表转换为相同类型的数组 .

  • 11

    我创建了这个代码片段,以反射方式实例化一个为简单的自动化测试实用程序传递的类 .

    Object attributeValue = null;
    try {
        if(clazz.isArray()){
            Class<?> arrayType = clazz.getComponentType();
            attributeValue = Array.newInstance(arrayType, 0);
        }
        else if(!clazz.isInterface()){
            attributeValue = BeanUtils.instantiateClass(clazz);
        }
    } catch (Exception e) {
        logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
    }
    

    请注意此细分:

    if(clazz.isArray()){
            Class<?> arrayType = clazz.getComponentType();
            attributeValue = Array.newInstance(arrayType, 0);
        }
    

    对于数组启动 Array.newInstance(class of array, size of array) . 类可以是原始(int.class)和对象(Integer.class) .

    BeanUtils是Spring的一部分 .

  • 1

    这包含在Effective Java, 2nd Edition的第5章(泛型)中,第25项...... Prefer lists to arrays

    您的代码将起作用,但它会生成未经检查的警告(您可以使用以下注释来抑制:

    @SuppressWarnings({"unchecked"})
    

    但是,使用List而不是Array可能会更好 .

    the OpenJDK project site上有一个关于这个bug /功能的有趣讨论 .

  • 0

    我发现了一种解决这个问题的方法 .

    下面的行抛出了泛型数组创建错误

    List<Person>[] personLists=new ArrayList<Person>()[10];
    

    但是,如果我将 List<Person> 封装在一个单独的类中,它就可以工作 .

    import java.util.ArrayList;
    import java.util.List;
    
    
    public class PersonList {
    
        List<Person> people;
    
        public PersonList()
        {
            people=new ArrayList<Person>();
        }
    }
    

    您可以通过getter公开PersonList类中的人员 . 下面的行将为您提供一个数组,每个元素都有一个 List<Person> . 换句话说 List<Person> 的数组 .

    PersonList[] personLists=new PersonList[10];
    

    在我正在研究的一些代码中,我需要这样的东西,这就是我为了让它工作而做的 . 到目前为止没有问题 .

  • 5

    您可以创建一个Object数组并将其转换为E到处 . 是的,它不是很干净的方式,但至少应该工作 .

  • 3

    传递值列表......

    public <T> T[] array(T... values) {
        return values;
    }
    
  • 3

    您不需要将Class参数传递给构造函数 . 试试这个 .

    static class GenSet<T> {
        private final T[] array;
        @SuppressWarnings("unchecked")
        public GenSet(int capacity, T... dummy) {
            if (dummy.length > 0)
                throw new IllegalArgumentException(
                  "Do not provide values for dummy argument.");
            Class<?> c = dummy.getClass().getComponentType();
            array = (T[])Array.newInstance(c, capacity);
        }
        @Override
        public String toString() {
            return "GenSet of " + array.getClass().getComponentType().getName()
                + "[" + array.length + "]";
        }
    }
    

    GenSet<Integer> intSet = new GenSet<>(3);
    System.out.println(intSet);
    System.out.println(new GenSet<String>(2));
    

    结果:

    GenSet of java.lang.Integer[3]
    GenSet of java.lang.String[2]
    
  • 10

    也许与这个问题无关但我在使用时遇到“ generic array creation ”错误

    Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];
    

    我用 @SuppressWarnings({"unchecked"}) 找到了以下作品(并为我工作):

    Tuple<Long, String>[] tupleArray = new Tuple[10];
    
  • 0

    你可以这样做:

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

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

    根据经验,只要在内部使用强制转换数组(例如,支持数据结构),并且不返回或暴露给客户端代码,此行为就是安全的 . 如果您需要将泛型类型的数组返回给其他代码,您提到的反射 Array 类是正确的方法 .


    值得一提的是,只要有可能,如果你有选择的话,你可以更愉快地使用 List 而不是数组,但使用集合框架会更加健壮 .

  • 3

    实际上,更简单的方法是创建一个对象数组并将其转换为您想要的类型,如下例所示:

    T[] array = (T[])new Object[SIZE];
    

    其中 SIZE 是常量, T 是类型标识符

  • 7

    没有人回答你发布的例子中发生了什么的问题 .

    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;
    }
    

    正如其他人所说,在编译过程中,泛型被“擦除” . 因此,在运行时,泛型的实例不知道其组件类型是什么 . 其原因是历史性的,Sun希望在不破坏现有接口(源和二进制)的情况下添加泛型 .

    另一方面,数组在运行时确实知道它们的组件类型 .

    这个例子解决了这个问题,让调用构造函数的代码(确实知道类型)传递一个参数,告诉类所需的类型 .

    因此,应用程序将使用类似的东西来构造类

    Stack<foo> = new Stack<foo>(foo.class,50)
    

    并且构造函数现在知道(在运行时)组件类型是什么,并且可以使用该信息通过反射API构造数组 .

    Array.newInstance(clazz, capacity);
    

    最后我们有一个类型转换,因为编译器无法知道 Array#newInstance() 返回的数组是正确的类型(即使我们知道) .

    这种风格有点难看,但它有时可能是创建通用类型的最不好的解决方案,这些类型需要在运行时因任何原因(创建数组或创建其组件类型的实例等)知道它们的组件类型 .

  • 0

    Java泛型通过在编译时检查类型并插入适当的强制转换来工作,但是擦除已编译文件中的类型 . 这使得通用库可以被代码使用,而这些代码通常不会在运行时找出类型 .

    public Stack(Class<T> clazz,int capacity) 构造函数要求您在运行时传递Class对象,这意味着类信息在运行时可用于需要它的代码 . Class<T> 表单意味着编译器将检查您传递的Class对象是否是类型T的Class对象 . 不是T的子类,不是T的超类,而是T的超类 .

    这意味着您可以在构造函数中创建相应类型的数组对象,这意味着您在集合中存储的对象类型将在它们添加到集合时检查其类型 .

  • 6

    该示例使用Java反射来创建数组 . 通常不建议这样做,因为它不是类型安全的 . 相反,你应该做的只是使用内部List,并完全避免使用数组 .

相关问题