1188

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

但我真的不明白发生了什么。

4

31 回答 31

759

作为回报,我必须问一个问题:您的GenSet“已检查”还是“未检查”?这意味着什么?

  • 检查强类型GenSet明确知道它包含什么类型的对象(即,它的构造函数是用参数显式调用的Class<E>,并且当方法被传递的参数不是 type 时,它​​们将抛出异常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 中一个已知且有意为之的泛型弱点:它是使用擦除实现的,因此“泛型”类不知道它们在运行时使用什么类型参数创建,因此无法提供类型-除非实现了某种明确的机制(类型检查),否则安全。

于 2009-02-09T22:19:31.197 回答
256

你可以这样做:

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

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

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


List值得一提的是,只要有可能,如果您使用泛型,使用 s 而不是使用数组时,您会更开心。当然,有时您别无选择,但使用集合框架要健壮得多。

于 2010-05-27T20:00:45.727 回答
65

以下是如何使用泛型来获取您正在寻找的类型的数组,同时保持类型安全(与其他答案相反,这将给您返回一个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您在并且数组中的值是正确的类型。aa

它通过使用类文字作为运行时类型标记来工作,如Java 教程中所述。编译器将类文字视为java.lang.Class. 要使用一个,只需在类的名称后面加上.class. 因此,String.class充当Class代表类的对象String。这也适用于接口、枚举、任意维数组(例如String[].class)、基元(例如int.class)和关键字void(即void.class)。

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

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

在构造函数内部,调用该方法cast会将传递的Object参数强制转换为调用该方法的Class对象所代表的类。调用静态方法返回一个数组newInstance,该数组的类型由作为第一个参数传递的对象表示,长度由作为第二个参数传递的对象指定。调用该方法返回一个对象,该对象表示由调用该方法的对象表示的数组的组件类型(例如,如果对象不表示数组)。java.lang.reflect.ArrayObjectClassintgetComponentTypeClassClassString.classString[].classnullClass

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

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

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

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


编辑关于 Ingo 的评论:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}
于 2010-11-19T03:30:15.870 回答
43

这是唯一类型安全的答案

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}
于 2011-11-08T15:28:17.957 回答
35

要扩展到更多维度,只需将[]'s 和 dimension 参数添加到newInstance()T是类型参数,cls是 a Class<T>d1通过d5是整数):

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()

于 2013-08-15T13:47:19.580 回答
16

在 Java 8 中,我们可以使用 lambda 或方法引用来创建一种通用数组。这类似于反射方法(通过 a 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 之前使用匿名类完成,但更麻烦。

于 2014-03-05T14:14:02.287 回答
13

这在Effective Java, 2nd Edition的第 5 章(泛型)第25 项中有所介绍...优先使用列表而不是数组

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

@SuppressWarnings({"unchecked"})

但是,使用列表而不是数组可能会更好。

在 OpenJDK 项目站点上有一个关于这个错误/功能的有趣讨论。

于 2009-02-09T18:50:42.883 回答
10

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

public class GenSet<T> {

    private final T[] array;

    @SafeVarargs
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        this.array = Arrays.copyOf(dummy, 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]
于 2017-07-11T21:53:55.897 回答
7

Java 泛型通过在编译时检查类型并插入适当的强制转换来工作,但会删除编译文件中的类型。这使得不理解泛型的代码可以使用泛型库(这是一个深思熟虑的设计决定),但这意味着您通常无法在运行时找出类型是什么。

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

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

于 2009-02-11T10:07:35.283 回答
7

尽管线程已死,但我想提请您注意这一点。

泛型用于编译期间的类型检查。因此,目的是检查

  • 进来的就是你需要的。
  • 你返回的就是消费者需要的。

检查这个:

在此处输入图像描述

编写泛型类时不要担心类型转换警告;使用时担心。

于 2011-06-14T19:26:00.757 回答
6

这个解决方案怎么样?

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

它有效,看起来太简单了,不可能是真的。有什么缺点吗?

于 2016-02-21T01:28:56.793 回答
5

该示例使用 Java 反射创建一个数组。通常不建议这样做,因为它不是类型安全的。相反,您应该做的只是使用内部列表,并完全避免使用数组。

于 2009-02-09T17:33:58.310 回答
5

另请查看此代码:

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

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

于 2013-08-08T23:32:27.820 回答
5

我找到了一种对我有用的快速简便的方法。请注意,我只在 Java JDK 8 上使用过它。我不知道它是否适用于以前的版本。

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

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

现在主要我们可以像这样创建数组:

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

        // ...

    }
}

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

于 2016-11-09T20:43:28.790 回答
5

传递值列表...

public <T> T[] array(T... values) {
    return values;
}
于 2017-09-15T10:19:41.983 回答
3

我制作了这个代码片段来反射地实例化一个为简单的自动化测试实用程序传递的类。

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

用于数组初始化 where Array.newInstance(class of array, size of array)。类既可以是原始(int.class)也可以是对象(Integer.class)。

BeanUtils 是 Spring 的一部分。

于 2012-08-31T10:39:28.523 回答
2

其他人建议的强制施法对我不起作用,抛出非法施法的异常。

但是,这种隐式转换效果很好:

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

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

private K value;

这样,您将获得 K 类型的数组(如果该项只有值)或您希望在 Item 类中定义的任何泛型类型。

于 2013-09-14T21:26:33.447 回答
2

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

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

其中SIZE是一个常数,T是一个类型标识符

于 2015-06-12T09:40:46.373 回答
1

没有其他人回答您发布的示例中发生了什么的问题。

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()是正确的类型(即使我们知道)。

这种风格有点难看,但它有时可能是创建在运行时出于任何原因(创建数组或创建其组件类型的实例等)确实需要知道其组件类型的泛型类型的最不坏的解决方案。

于 2015-10-17T05:49:51.347 回答
1

我找到了一种解决这个问题的方法。

下面的行抛出通用数组创建错误

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

在我正在处理的一些代码中,我需要这样的东西,这就是我为让它工作所做的。到目前为止没有问题。

于 2016-10-19T01:04:54.463 回答
1

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

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
于 2018-04-19T13:45:59.760 回答
0

您可以创建一个 Object 数组并将其转换为 E 无处不在。是的,这不是很干净的方法,但它至少应该有效。

于 2009-02-09T17:46:55.343 回答
0

试试这个。

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>();
        }
    }
}
于 2011-02-13T20:37:34.907 回答
0

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

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
于 2012-04-05T00:10:45.893 回答
0

也许与这个问题无关,但是当我得到“ generic array creation”错误使用时

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

我发现了以下作品(并为我工作) @SuppressWarnings({"unchecked"})

 Tuple<Long, String>[] tupleArray = new Tuple[10];
于 2013-08-21T16:11:35.523 回答
0

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

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 代码那样通用。

于 2014-07-09T13:36:35.787 回答
0

您可以使用演员表:

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

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}
于 2014-09-15T17:23:32.283 回答
0

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

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

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

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

启动 anew Generic Invoker[]将导致未经检查的问题,但实际上不应该有任何问题。

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

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

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

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

add 函数可以像这样添加:

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++;
}
于 2017-06-28T19:59:53.103 回答
0

如果您真的想包装一个固定大小的通用数组,您将有一个方法来向该数组添加数据,因此您可以在那里正确初始化数组,执行如下操作:

import java.lang.reflect.Array;

class Stack<T> {
    private T[] array = null;
    private final int capacity = 10; // fixed or pass it in the constructor
    private int pos = 0;

    public void push(T value) {
        if (value == null)
            throw new IllegalArgumentException("Stack does not accept nulls");
        if (array == null)
            array = (T[]) Array.newInstance(value.getClass(), capacity);
        // put logic: e.g.
        if(pos == capacity)
             throw new IllegalStateException("push on full stack");
        array[pos++] = value;
    }

    public T pop() throws IllegalStateException {
        if (pos == 0)
            throw new IllegalStateException("pop on empty stack");
        return array[--pos];
    }
}

在这种情况下,您使用 java.lang.reflect.Array.newInstance 创建数组,它不会是 Object[],而是真正的 T[]。您不必担心它不是最终的,因为它是在您的班级内部管理的。请注意,您需要 push() 上的非 null 对象才能获取要使用的类型,因此我添加了对您推送的数据的检查并在那里引发异常。

这仍然有些毫无意义:您通过 push 存储数据,它是保证只有 T 元素会进入的方法的签名。因此,数组是 Object[] 还是 T[] 或多或少无关紧要。

于 2019-11-06T13:18:31.517 回答
0

根据 vnportnoy 的语法

GenSet<Integer> intSet[] = new GenSet[3];

创建一个空引用数组,填充为

for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

这是类型安全的。

于 2020-05-22T22:22:55.653 回答
-1
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}
于 2015-06-03T05:52:51.570 回答