如何在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>
参数显式调用的,当它们传递非E
types的参数时,方法会抛出一个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的数组,并且你可以指定一个元素从a
types的variables,这意味着数组和数组中的值是正确的types。
它通过使用类文字作为运行时types标记来工作,正如Java教程中所讨论的。 类文字被编译器视为java.lang.Class
实例。 要使用它,只需按照.class
的类名称。 所以, String.class
充当表示类String
的Class
对象。 这也适用于接口,枚举,任何维数组(例如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
调用静态方法newInstance
以java.lang.reflect.Array
返回作为第一个parameter passing的Class
对象所表示的types的数组,以及作为第二个parameter passing的int
指定的长度。 调用方法getComponentType
返回一个Class
对象,该对象表示由调用该方法的Class
对象表示的数组的组件types(例如String[].class
String.class
,如果Class
对象不表示数组,则返回null
) 。
最后一句话并不完全准确。 调用String[].class.getComponentType()
返回表示类String
的Class
对象,但是它的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>
, 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()
。
有效的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]; }