如何在Java中创建通用数组?
由于Java泛型的实现,您不能有这样的代码:如何在Java中创建通用数组?,java,arrays,generics,reflection,instantiation,Java,Arrays,Generics,Reflection,Instantiation,由于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;
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;
}
但是我真的不明白发生了什么。这个例子是使用Java反射创建一个数组。通常不建议这样做,因为它不是类型安全的。相反,您应该做的只是使用一个内部列表,并且完全避免使用数组。您可以创建一个对象数组,并将其强制转换为E everywhere。是的,这不是一个非常干净的方法,但至少应该可以工作。这在第5章的泛型中有介绍,第25项…更喜欢列表而不是数组 您的代码将正常工作,尽管它将生成一个未经检查的警告,您可以使用以下注释抑制该警告:
@SuppressWarnings({"unchecked"})
但是,最好使用列表而不是数组
有一个关于这个错误/功能的有趣的讨论。我必须问一个问题作为回报:你的发电机组是检查了还是没有检查? 这是什么意思 选中:强输入。GenSet明确地知道它包含什么类型的对象,即它的构造函数是用类参数显式调用的,当传递的参数不是类型e时,方法将抛出异常。请参阅 ->在这种情况下,你应该写:
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];
}
}
未选中:弱键入。实际上没有对作为参数传递的任何对象进行类型检查
->那样的话,你应该写
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;
}
}
请注意,数组的组件类型应为类型参数的类型:
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中泛型的一个已知的、故意的弱点:它是使用擦除实现的,所以泛型类不知道在运行时用什么类型的参数创建的,因此,除非实现某种显式的机制类型检查,否则无法提供类型安全。Java泛型通过在编译时检查类型并插入适当的强制转换来工作,但会删除编译文件中的类型。这使得不理解泛型的代码可以使用泛型库,这是一个深思熟虑的设计决策,但这意味着您通常无法在运行时找到泛型的类型 公共StackClass clazz,int capacity构造函数要求您在运行时传递类对象,这意味着类信息在运行时可用于需要它的代码。类形式意味着编译器将检查您传递的类对象是否就是类型T的类对象。不是T的子类,也不是T的超类,而是确切的T 这意味着您可以在构造函数中创建适当类型的数组对象,这意味着您存储在集合中的对象类型将在添加到集合时检查其类型。您可以执行以下操作:
E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];
这是在有效的Java中实现泛型集合的建议方法之一;项目26。没有类型错误,无需重复强制转换数组。但是,这会触发警告,因为它具有潜在危险,应谨慎使用。如注释中所述,此对象[]现在伪装为我们的E[]类型,如果使用不安全,可能会导致意外错误或ClassCastException
根据经验,只要cast数组在内部使用(例如用于支持数据结构),并且不返回或暴露于客户机代码,这种行为就是安全的。如果您需要将泛型类型的数组返回给其他代码,那么您提到的反射数组类就是正确的方法
值得一提的是,如果您使用泛型,在任何可能的情况下,使用列表而不是数组都会让您感到更加愉快。当然,有时您没有选择,但使用collections框架要强大得多。以下是如何使用泛型来获得您要查找的类型的数组,同时保持类型安全性,而不是其他答案,这将返回对象数组或在编译时产生警告:
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];
}
}
类中返回类对象的每个方法也是如此
关于Joachim Sauer对我自己没有足够的声誉来评论它的评论,使用对t[]的强制转换的示例将导致警告,因为在这种情况下编译器无法保证类型安全
编辑关于国际非政府组织的评论:
public static <T> T[] newArray(Class<T[]> type, int size) {
return type.cast(Array.newInstance(type.getComponentType(), size));
}
试试这个
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>();
}
}
}
虽然这根线已经断了,但我想提请你注意这一点 泛型用于编译时的类型检查。因此,目的是检查 进来的就是你需要的。 您返回的是消费者需要的。 选中此项:
在编写泛型类时,不要担心类型转换警告;使用时请担心。这是唯一的类型安全答案
E[] a;
a = newArray(size);
@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
return Arrays.copyOf(array, length);
}
一个简单但混乱的解决方法是在内部嵌套第二个holder类 e,并使用它保存您的数据
public class Whatever<Thing>{
private class Holder<OtherThing>{
OtherThing thing;
}
public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
我制作这个代码片段是为了反射性地实例化一个类,该类被传递给一个简单的自动化测试实用程序
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.newInstanceclass为数组的大小。类可以是基元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;
}
它将任何类型对象的列表转换为相同类型的数组。要扩展到更多维度,只需将[]和维度参数添加到newInstance即可。T是类型参数,cls是类,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);
有关详细信息,请参阅。可能与此问题无关,但当我在使用时遇到一般数组创建错误
Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];
其他人建议的强制铸造对我不起作用,这是非法铸造的一个例外 但是,这种隐式转换效果很好:
Item<K>[] array = new Item[SIZE];
通过这种方式,如果项只有类项中要定义的值或任何泛型类型,就可以得到K类型的数组。在Java 8中,我们可以使用lambda或方法引用创建一种泛型数组。这类似于通过类的反射方法,但这里我们不使用反射
@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之前使用匿名类来完成,但它更麻烦。我想知道这段代码是否可以创建一个有效的泛型数组
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;
}
编辑:如果所需大小已知且较小,那么创建这样一个数组的另一种方法可能是将所需数量的空值输入zeroArray命令
尽管这显然不如使用createArray代码那样通用。您可以使用强制转换:
public class GenSet<Item> {
private Item[] a;
public GenSet(int s) {
a = (Item[]) new Object[s];
}
}
实际上,更简单的方法是创建一个对象数组,并将其转换为所需类型,如以下示例所示:
T[] array = (T[])new Object[SIZE];
其中SIZE是一个常量,T是一个类型标识符没有人回答您发布的示例中发生了什么问题
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;
}
最后我们有一个类型转换,因为编译器无法知道ArraynewInstance返回的数组是正确的类型,即使我们知道
这种样式有点难看,但有时它可能是创建泛型类型的最不坏的解决方案,无论出于何种原因,泛型类型在运行时确实需要知道其组件类型创建数组,或创建其组件类型的实例等等。这个解决方案怎么样
@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
return elems;
}
它是有效的,看起来太简单了,不可能是真的。有什么缺点吗?我找到了解决这个问题的办法 下面的行抛出一般数组创建错误
List<Person>[] personLists=new ArrayList<Person>()[10];
在我正在编写的一些代码中,我需要类似这样的东西,这就是我所做的,以使其工作。到目前为止还没有问题。我找到了一种适合我的快捷方法。注意,我只在JavaJDK8上使用过这个。我不知道它是否适用于以前的版本 尽管我们不能实例化特定类型参数的泛型数组,但我们可以将已经创建的数组传递给泛型类构造函数
class GenArray <T> {
private T theArray[]; // reference array
// ...
GenArray(T[] arr) {
theArray = arr;
}
// Do whatever with the array...
}
现在,我们主要可以这样创建阵列:
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类中的其他方法。我实际上找到了一个非常独特的解决方案,可以绕过无法启动通用数组的问题。您需要做的是创建一个类,该类接受通用变量T,如下所示:
class GenericInvoker <T> {
T variable;
public GenericInvoker(T variable){
this.variable = variable;
}
}
其余部分(如调整数组大小)可以使用Arrays.copyOf完成,如下所示:
public void resize(int newSize){
array = Arrays.copyOf(array, newSize);
}
添加功能可以这样添加:
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++;
}
您不需要将类参数传递给构造函数。 试试这个
public class GenSet<T> {
private final T[] array;
@SafeVarargs
public GenSet(int capacity, T... dummy) {
if (dummy.length > 0)
throw new IllegalArgumentException(
"Do not provide values for dummy argument.");
this.array = Arrays.copyOf(dummy, capacity);
}
@Override
public String toString() {
return "GenSet of " + array.getClass().getComponentType().getName()
+ "[" + array.length + "]";
}
}
正在传递值列表
public <T> T[] array(T... values) {
return values;
}
java中不允许创建泛型数组,但您可以像
class Stack<T> {
private final T[] array;
public Stack(int capacity) {
array = (T[]) new Object[capacity];
}
}
根据vnportnoy的语法
GenSet<Integer> intSet[] = new GenSet[3];
创建一个空引用数组,作为
for (int i = 0; i < 3; i++)
{
intSet[i] = new GenSet<Integer>();
}
这是类型安全的。您真的需要在这里使用数组吗?使用集合如何?是的,我也认为集合对于解决这个问题更为优雅。但这是一个类赋值,它们是必需的:我不明白为什么我需要一个reflect。Java语法很奇怪:比如new Java.util.HashMap[10]是无效的。新的java.util.HashMap10无效。新长[][10]无效,新长[]有效。这使得编写一个可以编写java程序的程序比看起来更难。第二个使用Array.newInstance的示例实际上是类型安全的。这是可能的,因为类对象的类型T需要与数组的类型T匹配。它基本上迫使您提供Java运行时丢弃的泛型信息;在上述试验方法中。这是因为E的数组不是真的,它是Object[]。如果您想要,这很重要,例如列表[]-您不能使用对象[],您必须拥有列表[]
明确地这就是您需要使用反射类数组创建的原因。最关键的情况/问题是,当internalArray类型为E[],因此实际上是一个对象[]时,您是否希望执行公共E[]toArray{return E[]internalArray.clone;}。由于无法将对象[]分配给任何类型的数组,因此在运行时由于类型强制转换异常而失败。基本上,只要不返回数组、传递数组或将其存储在需要特定类型数组的类之外的某个位置,这种方法就可以工作。只要你在教室里,你就没事,因为E被擦除了。这是危险的,因为如果你试图归还它或其他东西,你不会得到不安全的警告。但是如果你小心的话,它会工作的。它是相当安全的。在E[]中,b=E[]新对象[1];您可以清楚地看到,对所创建数组的唯一引用是b,b的类型是E[]。因此,不存在通过不同类型的不同变量意外访问同一数组的危险。如果相反,则对象[]a=新对象[1];E[]b=E[]a;那么,您就需要对如何使用a心存疑虑。至少在Java 1.6中,这会产生一个警告:未选中从Object[]到T[]的强制转换这是没有用的,这只是编写新字符串[…]的一种复杂方式。但真正需要的是像public static T[]newArrayint size{…}这样的东西,而这在java noir中根本不存在。它可以通过反射进行模拟吗?原因是关于泛型类型如何实例化的信息在运行时不可用。@Ingo你在说什么?我的代码可以用来创建任何类型的数组。@江湖骗子:当然可以,但new[]也可以。问题是:谁知道类型和时间。因此,如果你只有一个泛型类型,你不能。我不怀疑这一点。关键是,对于泛型类型X,在运行时不会得到类对象。我承认,这比使用新[]所能实现的还要多。在实践中,这几乎总能完成任务。但是,例如,仍然不可能编写一个用E参数化的容器类,该容器类具有方法E[]toArray,并且确实返回一个真正的E[]数组。只有当集合中至少有一个电子对象时,才能应用代码。因此,一般的解决方案是不可能的。性能方面的最佳选择是什么?我需要在循环中经常从这个数组中获取元素。所以一个集合可能较慢,但这两个集合中哪一个最快?如果泛型类型是有界的,则支持数组应为有界类型。@Aarondigula只是为了澄清这不是赋值,而是局部变量的初始化。您不能对表达式/语句进行注释。@Varkhan有办法从类实现中调整这些数组的大小。例如,如果我想像ArrayList一样在溢出后调整大小。我查找了ArrayList的实现,它们有对象[]EMPTY_ELEMENTDATA={}用于存储。我可以在不知道使用泛型的类型的情况下使用这种机制来调整大小吗?对于那些想要使用泛型类型(我正在寻找的类型)来创建方法的人,使用这个:public void T[]newArrayClass type,int length{…}我必须查找它,但是是的,ArrayscopyOf的第二个长度参数独立于作为第一个参数提供的数组的长度。这很聪明,尽管它确实支付了调用Mathmin和SystemarrayCopy的费用,但这两者都不是完成这项工作所必须的。如果E是一个类型变量,则这不起作用。当E是一个类型变量时,varargs会创建一个擦除E的数组,使其与E[]新对象[n]没有太大区别。请看。它绝不比任何其他答案更安全。@Radiodef-该解决方案在编译时是可证明的类型安全的。请注意,擦除并不完全是语言规范的一部分;规范编写得很仔细,这样我们将来就可以进行完全的具体化,然后这个解决方案也可以在运行时完美地工作,这与其他解决方案不同。@Radiodef-禁止通用数组创建是否是一个好主意还存在争议。不管怎样,该语言确实留下了后门——vararg需要创建通用数组。这就像语言允许新的E[]一样好。您在示例中显示的问题是一个一般的擦除问题,并非此问题和此答案所独有。@Radiodef-存在一些差异。编译器检查此解决方案的正确性;它不依赖于人类对强制施法的推理。对于这个特定的问题,差异并不显著。有些人只是喜欢有点幻想,仅此而已。如果有人被OP的措辞所误导,你的评论和我的评论都会澄清这一点。+1关于多维数组创建的一些问题,在这篇文章中都被揭穿了,但没有任何答案能具体说明这一点。@JordanC;虽然在精神上与,;我会考虑明天最好的处理方式。我困了。是的,这不是
非常相关,但根源相同的问题是擦除、数组协方差。下面是一篇关于创建参数化类型数组的文章的示例:是的,返回null,这不是预期的空数组。这是您所能做的最好的,但并不理想。如果列表中有多个类型的对象,例如ToArrayArray,则此操作也可能失败。asListabc,新对象将抛出ArrayStoreException。我使用了此操作的精简版本;我能够使用的第一件事是有效的,尽管承认我没有尝试一些更复杂的解决方案。为了避免for循环和其他循环,我使用了Arrays.fillres,obj;因为我希望每个索引都有相同的值。这实际上不起作用。new Holder[10]是一个通用数组创建。我们正在寻找提供一些解释和上下文的详细答案。不要只给出一句话的答案;解释为什么你的答案是正确的,最好是引用。没有解释的答案可能会被删除。但在某些情况下,如您的泛型类想要实现类似的接口,这将不起作用。我想,欢迎使用七年前的版本。如果您试图将数组从泛型代码返回给非泛型调用方,这将不起作用。将会有一个head scratting classcastexception。如果你打算提出这个建议,你真的需要解释一下它的局限性。永远不要在课堂外接触任何东西!不,这不起作用。当T是类型变量时,varargs创建T的擦除,即zeroArray返回一个对象[]。请参阅。您应该始终在代码中添加解释,并解释为什么它可以解决最初发布的问题。为此,您不需要像ArraySupplier这样的特殊接口,您可以将构造函数声明为GenSetSupplier{…并使用与您相同的行调用它。@Lii要与我的示例相同,它应该是IntFunction,但这是正确的。整洁,但只有在您“手动”调用它时才有效,即单独传递元素。如果您无法创建t[]的新实例,则无法通过编程构建t[]元素传递到函数中。如果可以,则不需要该函数。问题是创建泛型类型参数t类型的数组,而不是某个参数化类型的数组。虽然它完成了相同的任务,但不需要推入类以使自定义集合更易于使用。什么任务?它被点亮了通常是一个不同的任务:参数化类型的数组与泛型类型参数的数组。它允许您从泛型类型创建数组?最初的问题是使用泛型类型初始化数组,使用我的方法,您无需让用户推入类或给出未检查的错误(如尝试)将一个对象转换为字符串。像chill一样,我并不擅长我所做的,我也没有上过编程学校,但我认为我仍然值得一点投入,而不是在互联网上被其他孩子斥责。我同意Sotiros的观点。有两种方式来思考答案。要么是对不同问题的回答,要么是一个问题试图概括这个问题。两者都是错误的/没有帮助的。那些正在寻找关于如何实现泛型数组类的指导的人在阅读问题标题时会/停止阅读。当他们发现一个有30个答案的Q时,他们极不可能滚动到最后,从一个如此新的人那里读到零票答案。我无法理解代码运行时,元素类从何而来?
class GenericInvoker <T> {
T variable;
public GenericInvoker(T variable){
this.variable = variable;
}
}
GenericInvoker<T>[] array;
public MyArray(){
array = new GenericInvoker[];
}
public T get(int index){
return array[index].variable;
}
public void resize(int newSize){
array = Arrays.copyOf(array, newSize);
}
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++;
}
public class GenSet<T> {
private final T[] array;
@SafeVarargs
public GenSet(int capacity, T... dummy) {
if (dummy.length > 0)
throw new IllegalArgumentException(
"Do not provide values for dummy argument.");
this.array = Arrays.copyOf(dummy, 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;
}
class Stack<T> {
private final T[] array;
public Stack(int capacity) {
array = (T[]) new Object[capacity];
}
}
GenSet<Integer> intSet[] = new GenSet[3];
for (int i = 0; i < 3; i++)
{
intSet[i] = new GenSet<Integer>();
}