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

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

但是我真的不明白正在发生什么。

21
这里真的需要使用数组吗?使用集合类(Collection)如何? - matt b
16
我也认为使用集合更优雅解决这个问题。但由于这是一项课程作业,必须使用它们。 - tatsuhirosatou
5
我不明白为什么这里需要一个反射。Java语法很奇怪:例如new java.util.HashMap<String,String>[10]是无效的,new java.util.HashMap<long,long>(10)也是无效的,new long[][10]是无效的,但是new long[10][]是有效的。这些内容使编写一个能够编写Java程序的程序比看起来更加困难。 - bronze man
非常令人惊讶的是,一个如此重要的功能(Java 20)竟然还没有一个优雅的解决方案。 - undefined
32个回答

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

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


1
根据vnportnoy的语法。
GenSet<Integer> intSet[] = new GenSet[3];

创建一个空引用数组,以便填充。
for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

这是类型安全的。


0
一个简单但有点凌乱的解决方法是在主类中嵌套第二个“holder”类,并使用它来保存数据。
public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}

3
这实际上是行不通的。new Holder<Thing>[10]是一个泛型数组的创建。 - Radiodef

0

我实际上找到了一个非常独特的解决方案,可以绕过初始化通用数组的无能为力。你需要做的是创建一个类,该类接受通用变量T,如下所示:

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

然后在你的数组类中,只需要这样开始:

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

启动一个新的通用调用者[]会导致未检查的问题,但实际上不应该有任何问题。

要从数组中获取,您应该这样调用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++;
}

1
问题是关于创建一个泛型类型参数 T 的数组,而不是某个参数化类型的数组。 - Sotirios Delimanolis
它完成了相同的任务,而且不需要您推入一个类,使您的自定义集合更易于使用。 - Crab Nebula
什么“任务”?这实际上是一个不同的任务:参数化类型数组与泛型类型参数数组。 - Sotirios Delimanolis
它允许您从通用类型创建数组?最初的问题是使用通用类型初始化数组,使用我的方法可以使您无需让用户推入类或给出未经检查的错误,例如尝试将对象强制转换为字符串。就像放松一样,我不是我所做的最好的人,我也没有上过编程学校,但我认为我仍然应该得到一些输入,而不是被互联网上的其他孩子告诉。 - Crab Nebula
我同意Sotiros的观点。有两种思考答案的方式。一种是它是对不同问题的回答,另一种是试图概括问题。这两种方式都是错误的/没有帮助的。那些正在寻求如何实现“通用数组”类的指导的人们,在阅读问题标题时可能会停止阅读。当他们发现一个有30个答案的问题时,他们极不可能滚动到最后并阅读来自SO新手的零票答案。 - Stephen C
最后,如果他们“自然地”找到了你的A并且它是相关的,那么他们很幸运。你把它“归档”到了错误的位置。 - Stephen C

0

数组不支持泛型(因为它是另一种类型的数据),但是如果您不需要转换,则可以在创建时使用未确定的泛型,顺便说一下,这比使用反射要好:

List<?>[] chars = new List[3];

现在,我们即使没有Unchecked type警告,也可以获得合法的泛型数组。


0
你可以创建一个对象数组并将其转换为 E。是的,这不是很干净的方法,但至少应该能够工作。

我们希望得到提供一些解释和背景的长回答。不要只给出一句话的答案;最好用引用来解释你的答案为什么是正确的。没有解释的答案可能会被删除。 - gparyani
但在某些情况下,这种方法不起作用,例如如果您的泛型类想要实现Comparable接口。 - RamPrasadBismil
欢迎来到七年前,我想。 - Esko
1
如果您尝试将数组从通用代码返回给非通用调用者,则此方法将无法工作。它会导致一个让人困惑的类转换异常。 - plugwash

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代码灵活多变。


不,这样行不通。当T是一个类型变量时,可变参数会擦除T,即zeroArray返回一个Object[]。请参见http://ideone.com/T8xF91。 - Radiodef

0
你可以使用类型转换:
public class GenSet<Item> {
    private Item[] a;

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}

如果你要建议这个,你真的需要解释它的限制。永远不要将 a 暴露给类外部! - Radiodef

0
也许与这个问题无关,但是当我因为使用泛型数组创建代码而出现“generic array creation”错误时
Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

我发现以下代码(对我有效)可以使用 @SuppressWarnings({"unchecked"})

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

是的,这与问题相关性不大,但根源在于相同的问题(擦除、数组协变)。以下是有关创建参数化类型的数组的帖子示例:http://stackoverflow.com/questions/9542076/array-of-parameterized-types - Paul Bellora

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()上有一个非空对象才能获取要使用的类型,因此我在您推送的数据上添加了一个检查并在那里抛出异常。
尽管如此,这有点毫无意义:您通过push存储数据,并且方法的签名保证只有T元素将进入。因此,数组是Object[]还是T[]更或多或少是无关紧要的。

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