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

How to create a generic array in Java?

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

1
2
3
4
5
6
7
public class GenSet<E> {
    private E a[];

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

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

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

1
2
3
4
5
6
7
8
9
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类型时,它们将抛出异常。见Collections.checkedCollection

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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];
        }
    }
  • 未选中:键入错误。实际上没有对作为参数传递的任何对象进行类型检查。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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;
        }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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中泛型的一个已知的、蓄意的弱点:它是用擦除实现的,因此"泛型"类不知道它们在运行时创建了什么类型的参数,因此不能实现类型安全,除非实现一些显式机制(类型检查)。


您可以这样做:

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

这是在有效Java中实现泛型集合的建议方法之一;项目26。没有类型错误,不需要重复强制转换数组。但是,这会触发一个警告,因为它可能具有危险性,因此应小心使用。如评论中所述,此Object[]现在伪装成我们的E[]类型,如果使用不当,可能导致意外错误或ClassCastException

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

值得一提的是,如果您使用泛型,那么在使用List时,您将比使用数组时更快乐。当然,有时您没有选择,但是使用集合框架要强大得多。


下面介绍如何在保持类型安全性的同时,使用泛型精确地获取所需类型的数组(与其他答案不同,后者要么返回一个Object数组,要么在编译时导致警告):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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充当Class对象,代表String类。这也适用于接口、枚举、任何维数组(如String[].class、原语(如int.class)和关键字void(即void.class)。

Class本身是通用的(声明为Class,其中T表示Class对象所代表的类型,表示String.class的类型是Class类型。

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

在构造函数内部,调用方法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,这就是您不能执行以下操作的原因。

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

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

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

编辑关于国际非政府组织的评论:

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


这是唯一一个类型安全的答案

1
2
3
4
5
6
7
8
9
E[] a;

a = newArray(size);

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


要扩展到更多维度,只需将[]和维度参数相加到newInstance()(T是类型参数,clsClassd1通过d5是整数):

1
2
3
4
5
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()


在Java 8中,我们可以使用lambda或方法引用进行一种通用数组创建。这类似于反射方法(它通过一个Class),但这里我们不使用反射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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[] Stream.toArray(IntFunction)使用了这个。

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


这在第5章(泛型)中包含了有效的Java,第二版,第25条…更喜欢列表到数组。

您的代码将工作,尽管它将生成一个未选中的警告(您可以使用以下注释取消该警告:

1
@SuppressWarnings({"unchecked"})

但是,最好使用列表而不是数组。

在OpenJDK项目站点上有一个关于这个bug/特性的有趣讨论。


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

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

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


嗨,虽然线死了,但我想提醒您注意:

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

  • 因此,目的是检查进来的是你需要的。
  • 你的回报是消费者需要的。
  • 检查一下:

enter image description here

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


这个解决方案怎么样?

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

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


另请参阅此代码:

1
2
3
4
5
6
7
8
9
10
11
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;
}

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


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

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

1
2
3
4
5
6
7
8
9
10
11
class GenArray <T> {
    private T theArray[]; // reference array

    // ...

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

    // Do whatever with the array...
}

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

1
2
3
4
5
6
7
8
9
10
11
12
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类中的其他方法。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public 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 +"]";
    }
}

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

结果:

1
2
GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]

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


传递值列表…

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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});
}

请注意此段:

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

用于数组初始化,其中array.newInstance(array的类,array的大小)。类可以是基元(int.class)和对象(integer.class)。

豆豆是春天的一部分。


实际上,这样做的一个更简单的方法是创建一个对象数组并将其强制转换为所需的类型,如下面的示例所示:

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

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


其他人建议的强制演员阵容对我来说不起作用,这也排除了非法演员阵容。

然而,这种隐式演员很好地发挥了作用:

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

其中,item是包含成员的I类:

1
private K value;

这样,您就可以得到一个k类型的数组(如果该项只有值)或者您想要在类项中定义的任何泛型类型。


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

1
2
3
4
5
6
7
8
9
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希望在不破坏现有接口(源接口和二进制接口)的情况下添加泛型。

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

这个例子通过让调用构造函数(它知道类型)的代码传递一个参数来告诉类所需的类型来解决这个问题。

因此,应用程序将使用

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

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

1
Array.newInstance(clazz, capacity);

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

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


我找到了解决这个问题的办法。

下面的行引发了一般数组创建错误

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

但是,如果我将List封装在一个单独的类中,它就工作了。

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

你可以通过getter公开类中的人员列表。下面的行将为您提供一个数组,每个元素中都有一个List。换言之,List的数组。

1
PersonList[] personLists=new PersonList[10];

在我正在编写的代码中,我需要这样的东西,这就是我为使它正常工作所做的。到目前为止没有问题。


解决这个问题的一个简单的方法是在主类中嵌套第二个"holder"类,并使用它来保存数据。

1
2
3
4
5
6
public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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代码那样通用。


试试这个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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>();
        }
    }
}


您可以使用演员表:

1
2
3
4
5
6
7
public class GenSet<Item> {
    private Item[] a;

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


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

1
2
3
4
5
6
class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

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

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

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

要从数组中获取,应调用数组[i]。变量如下:

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

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

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

添加功能可以这样添加:

1
2
3
4
5
6
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++;
}


您可以创建一个对象数组,并将其强制转换为E。是的,这不是一个很干净的方法,但至少应该有效。


可能与这个问题无关,但当我使用"generic array creation"时

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

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

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


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

1
2
3
4
5
6
class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}

1
2
3
4
5
6
7
8
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}