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 |
但我真的不知道发生了什么。
作为回报,我必须问一个问题:你的
选中:强输入。
GenSet 明确知道它包含的对象类型(即它的构造函数是用Class 参数显式调用的),当方法传递的参数不是E 类型时,它们将抛出异常。见Collections.checkedCollection 。->在这种情况下,你应该写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public 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请注意,数组的组件类型应为类型参数的擦除:
1
2
3
4
5
6
7
8
9
10public 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 |
这是在有效Java中实现泛型集合的建议方法之一;项目26。没有类型错误,不需要重复强制转换数组。但是,这会触发一个警告,因为它可能具有危险性,因此应小心使用。如评论中所述,此
根据经验,只要在内部使用强制转换数组(例如,支持数据结构),而不返回或向客户机代码公开,这种行为是安全的。如果您需要将一个泛型类型的数组返回到其他代码,那么您提到的反射
值得一提的是,如果您使用泛型,那么在使用
下面介绍如何在保持类型安全性的同时,使用泛型精确地获取所需类型的数组(与其他答案不同,后者要么返回一个
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]; } } |
它编译时没有警告,正如您在
它通过使用类文字作为运行时类型令牌来工作,如Java教程中所讨论的。类文本被编译器视为
因此,无论何时调用EDOCX1的构造函数(2),都要为表示
在构造函数内部,调用方法
最后一句话不完全准确。调用
对于返回
对于Joachim Sauer对这个答案的评论(我自己没有足够的声誉对此进行评论),使用cast to
编辑关于国际非政府组织的评论:
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); } |
要扩展到更多维度,只需将
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); |
详见
在Java 8中,我们可以使用lambda或方法引用进行一种通用数组创建。这类似于反射方法(它通过一个
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); } } |
例如,
这也可以在Java 8使用匿名类之前完成,但是它更麻烦。
这在第5章(泛型)中包含了有效的Java,第二版,第25条…更喜欢列表到数组。
您的代码将工作,尽管它将生成一个未选中的警告(您可以使用以下注释取消该警告:
1 | @SuppressWarnings({"unchecked"}) |
但是,最好使用列表而不是数组。
在OpenJDK项目站点上有一个关于这个bug/特性的有趣讨论。
Java泛型通过在编译时检查类型并插入适当的转换来工作,但是擦除编译文件中的类型。这使得不理解泛型(这是一个深思熟虑的设计决策)的代码可以使用泛型库,但这意味着您通常无法了解运行时的类型。
公共
这意味着您可以在构造函数中创建适当类型的数组对象,这意味着您存储在集合中的对象的类型将在添加到集合中时检查它们的类型。
嗨,虽然线死了,但我想提醒您注意:
泛型用于在编译期间进行类型检查:
- 因此,目的是检查进来的是你需要的。
- 你的回报是消费者需要的。
- 检查一下:
在编写泛型类时,不要担心类型转换警告。使用时要担心。
这个解决方案怎么样?
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 |
结果:
该示例使用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 |
其中
其他人建议的强制演员阵容对我来说不起作用,这也排除了非法演员阵容。
然而,这种隐式演员很好地发挥了作用:
1 | Item<K>[] array = new Item[SIZE]; |
其中,item是包含成员的I类:
1 | private K value; |
这样,您就可以得到一个k类型的数组(如果该项只有值)或者您想要在类项中定义的任何泛型类型。
没有其他人回答过您发布的示例中发生了什么问题。
1 2 3 4 5 6 7 8 9 |
正如其他人所说,仿制药在编译过程中被"删除"。所以在运行时,泛型的一个实例不知道它的组件类型是什么。原因是历史,Sun希望在不破坏现有接口(源接口和二进制接口)的情况下添加泛型。
另一方面,数组在运行时确实知道它们的组件类型。
这个例子通过让调用构造函数(它知道类型)的代码传递一个参数来告诉类所需的类型来解决这个问题。
因此,应用程序将使用
1 | Stack<foo> = new Stack<foo>(foo.class,50) |
构造函数现在知道(在运行时)组件类型是什么,并且可以使用这些信息通过反射API构造数组。
1 |
最后,我们有一个类型转换,因为编译器无法知道
这种样式有点难看,但有时对于创建那些在运行时出于任何原因(创建数组或创建其组件类型的实例等)需要知道其组件类型的泛型类型来说,它可能是最不坏的解决方案。
我找到了解决这个问题的办法。
下面的行引发了一般数组创建错误
1 | List<Person>[] personLists=new ArrayList<Person>()[10]; |
但是,如果我将
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公开类中的人员列表。下面的行将为您提供一个数组,每个元素中都有一个
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 |
添加功能可以这样添加:
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。是的,这不是一个很干净的方法,但至少应该有效。
可能与这个问题无关,但当我使用"
我和
1 |
在爪哇中不允许通用数组创建,但您可以这样做
1 2 3 4 5 6 |
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]; } |