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

由于Javagenerics的实现,你不能有这样的代码:

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

我怎样才能实现这一点,同时保持types安全?

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

 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明确地知道它包含的是什么types的对象(即它的构造函数是用Class<E>参数显式调用的,当它们传递非Etypes的参数时,方法会抛出一个exception,参见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]; } } 
  • 未经检查打字弱 。 没有任何types检查实际上是作为parameter passing的任何对象。

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

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

    请注意,数组的types应该是types参数的删除

     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中generics的一个已知和故意的弱点:它使用擦除来实现,所以“generics”类不知道它们在运行时创build的types参数,因此不能提供types – 除非有一些明确的机制(types检查)被执行。

你总是可以这样做:

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

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

作为一个经验法则,只要cast数组在内部使用(例如,返回数据结构),此行为是安全的,而不是返回或暴露给客户端代码。 如果您需要将一个genericstypes的数组返回给其他代码,那么您提到的reflectionArray类是正确的方法。


值得一提的是,只要有可能,如果您使用的是generics,那么在使用List s而不是数组时,您将会有更快乐的时间。 当然,有时你没有select,但使用集合框架是更加强大的。

下面是如何在保留types安全性的同时,使用generics来获取一个精确的types数组(而不是其他的答案,这些答案会让你返回一个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实例的types,你都可以指定一个types的数组,并且你可以指定一个元素从atypes的variables,这意味着数组和数组中的值是正确的types。

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

Class本身是generics的(声明为Class<T> ,其中T表示Class对象表示的types),这意味着String.class的types是Class<String>

因此,无论何时调用GenSet的构造函数,都会为第一个parameter passing一个types文本,表示GenSet实例声明types的数组(例如, String[].class for GenSet<String> )。 请注意,由于原语不能用于typesvariables,因此您将无法获取原始数组。

在构造函数中,调用方法cast会将传递的Object参数cast转换为调用该方法的Class对象表示的Class 。 在java.lang.reflect.Array调用静态方法newInstancejava.lang.reflect.Array返回作为第一个parameter passing的Class对象所表示的types的数组,以及作为第二个parameter passing的int指定的长度。 调用方法getComponentType返回一个Class对象,该对象表示由调用该方法的Class对象表示的数组的组件types(例如String[].class String.class ,如果Class对象不表示数组,则返回null ) 。

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

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

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

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


编辑关于Ingo的评论:

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

这是types安全的唯一答案

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

为了扩展到更多的维度,只需要添加[]和尺寸参数到newInstance()T是一个types参数, cls是一个Class<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()

有效的Java,第2版 , 第 25项的第5章(generics)中介绍​​了这一点。 首选列表到数组

你的代码将起作用,虽然它会产生一个未经检查的警告(你可以用下面的注解来压制:

 @SuppressWarnings({"unchecked"}) 

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

在OpenJDK项目网站上有一个有趣的讨论这个bug /function。

在Java 8中,我们可以使用lambda或方法引用来创build一种通用数组。 这与reflection方法类似(它传递一个Class ),但在这里我们没有使用reflection。

 @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之前完成,但是更麻烦。

Javagenerics通过在编译时检查types和插入适当的强制types来工作,但是擦除编译文件中的types。 这使通用库可用于不懂generics的代码(这是一个有意的devise决定),但这意味着在运行时通常无法findtypes。

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

这就意味着你可以在你的构造函数中创build一个适当types的数组对象,这意味着你存储在你的集合中的对象的types将在他们被添加到集合的时候检查它们的types。

嗨,虽然线程已经死了,但我想请你注意一下:

generics用于编译期间的types检查:

  • 因此,目的是检查进来的是你需要的。
  • 你回报什么是消费者需要的。
  • 检查这个:

在这里输入图像描述

在编写generics类时,不要担心types警告。 担心,当你使用它。

这个解决scheme呢?

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

它工作,看起来太简单,不真实。 有什么缺点吗?

这个例子是使用Javareflection来创build一个数组。 一般不build议这样做,因为它不是types安全的。 相反,你应该做的只是使用一个内部的列表,并根本避免数组。

我使这个代码片段reflection性地实例化一个简单的自动化testing实用程序传递的类。

 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(数组的类,数组的大小) 。 类可以是原始(int.class)和对象(Integer.class)。

BeanUtils是Spring的一部分。

看看这个代码:

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

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

我发现了一个快速而简单的方法,适合我。 请注意,我只在Java JDK 8上使用过这个function。我不知道它是否适用于以前的版本。

虽然我们不能实例化一个特定types参数的generics数组,但我们可以将已经创build的数组传递给generics类的构造函数。

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

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

 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类中的其他方法。

您不需要将Classparameter passing给构造函数。 尝试这个。

 static class GenSet<T> { private final T[] array; @SuppressWarnings("unchecked") public GenSet(int capacity, T... dummy) { 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] 

传递值列表

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

其他人提出的强制转换对我来说并不起作用,抛出了非法铸造的例外。

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

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

Item是我定义的一个包含成员的类:

 private K value; 

这样你得到一个types为K的数组(如果该项只有值)或者你想在类Item中定义的任何genericstypes。

实际上,更简单的方法是创build一个对象数组并将其转换为所需的types,如下例所示:

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

SIZE是一个常量, T是一个types标识符

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

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

正如其他人所说的,仿制药在编辑过程中被“抹去”了。 所以在运行时,一个generics的实例不知道它的组件types是什么。 原因在于历史,Sun希望在不破坏现有界面(源代码和二进制文件)的情况下添加generics。

另一方面,数组在运行时知道它们的组件types。

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

所以应用程序会用类似的东西构造类

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

并且构造函数现在知道(在运行时)什么是组件types,并且可以使用该信息通过reflectionAPI来构造数组。

 Array.newInstance(clazz, capacity); 

最后我们有一个types转换,因为编译器无法知道由Array#newInstance()返回的Array#newInstance()是否是正确的types(尽pipe我们知道)。

这种风格有点难看,但有时候可能是创build通用types的最不好的解决scheme,无论出于何种原因(创build数组或创build其组件types的实例等),都需要在运行时知道其组件types。

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

下面的代码会抛出通用数组创build错误

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

在我正在做的一些代码中,我需要这样的东西,而这正是我所做的。 到目前为止没有问题。

你可以创build一个Object数组,并将它投射到E处。 是的,这不是很干净的方式,但至less应该工作。

尝试这个。

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

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

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

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

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

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

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

我想知道如果这个代码会创build一个有效的通用数组?

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

编辑:也许创build这样一个数组的另一种方法,如果你需要的大小是已知和小,将简单地将所需数量的“null”喂入zeroArray命令?

显然,这不像使用createArray代码那样通用。

你可以使用演员:

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

我实际上find了一个非常独特的解决scheme来绕过无法启动一个通用数组。 你需要做的就是创build一个接受genericsvariablesT的类,如下所示:

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

添加function可以像这样添加:

 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++; } 
 private E a[]; private int size; public GenSet(int elem) { size = elem; a = (E[]) new E[size]; }