如何在 Java 中创建通用数组?
由于实现了Java Generic,您不能拥有这样的代码:
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;
}
这是怎么回事?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
我必须问一个问题:您的
Genset
“检查”或“未检查”?这意味着什么?
检查:强键入。
Genset
明确知道其包含哪些类型的对象(即,其构造函数被用class&lt; e&gt;
参数明确调用,并且当方法传递的参数时,方法将引发一个例外不是类型e
.Collection,%20Java.lang.Class%29“ rel =“ noreferrer”>collections.checkedCollection
。。- &gt;在这种情况下,您应该写:
未选中的:弱打字实际上没有进行任何类型检查。
- &gt;在这种情况下,您应该写
请注意,数组的组件类型应为 类型参数:
所有这些结果都是由java中的已知且故意的,刻意的,刻意的,含义的弱点:它是使用擦除实现的,所以“仿制药”类不知道它们在运行时创建了什么类型的参数,因此除非实现某些明确的机制(类型检查),否则无法提供类型安全。
I have to ask a question in return: is your
GenSet
"checked" or "unchecked"?What does that mean?
Checked: strong typing.
GenSet
knows explicitly what type of objects it contains (i.e. its constructor was explicitly called with aClass<E>
argument, and methods will throw an exception when they are passed arguments that are not of typeE
. SeeCollections.checkedCollection
.-> in that case, you should write:
Unchecked: weak typing. No type checking is actually done on any of the objects passed as argument.
-> in that case, you should write
Note that the component type of the array should be the erasure of the type parameter:
All of this results from a known, and deliberate, weakness of generics in Java: it was implemented using erasure, so "generic" classes don't know what type argument they were created with at run time, and therefore can not provide type-safety unless some explicit mechanism (type-checking) is implemented.
你可以这样做:
这是在Effective Java中实现通用集合的建议方法之一;第 26 项。没有类型错误,无需重复转换数组。 但是这会触发警告,因为它具有潜在危险,应谨慎使用。正如评论中详细说明的,此
Object[]
现在伪装成我们的E[]
类型,并且可能会导致意外错误或ClassCastException
,如果使用不安全。根据经验,只要强制转换数组在内部使用(例如支持数据结构),并且不返回或暴露给客户端代码,这种行为就是安全的。如果您需要将泛型类型的数组返回给其他代码,您提到的反射 Array 类是正确的方法。
值得一提的是,如果您使用泛型,那么只要有可能,您会更愉快地使用
List
而不是数组。当然,有时您别无选择,但使用集合框架要健壮得多。You can do this:
This is one of the suggested ways of implementing a generic collection in Effective Java; Item 26. No type errors, no need to cast the array repeatedly. However this triggers a warning because it is potentially dangerous, and should be used with caution. As detailed in the comments, this
Object[]
is now masquerading as ourE[]
type, and can cause unexpected errors orClassCastException
s if used unsafely.As a rule of thumb, this behavior is safe as long as the cast array is used internally (e.g. to back a data structure), and not returned or exposed to client code. Should you need to return an array of a generic type to other code, the reflection
Array
class you mention is the right way to go.Worth mentioning that wherever possible, you'll have a much happier time working with
List
s rather than arrays if you're using generics. Certainly sometimes you don't have a choice, but using the collections framework is far more robust.这是使用仿制药在保存类型安全时的精确类型的方法(与其他答案相反,这要么将您带回
object
array或导致警告在编译时):无警告的编译,以及您在
main
中看到的,对于您声明Genset
的实例,您可以分配a
a
到该类型的数组,您可以将一个元素从a
分配给该类型的变量,这意味着数组和数组中的值是正确的类型。如 java)教程。班级文字被编译器视为
java.lang.class
的实例。要使用一个,只需按照.class
的类别遵循类的名称。因此,string.class
充当类
代表类String
的对象。这也适用于接口,枚举,任何维数组(例如string []。class
),primitives(例如int.class
)和关键字void
(即void.class
)。类
本身是通用的(称为class&lt; t&gt;
,其中t
代表class> class
对象的类型是表示),这意味着string.class
的类型是class&lt; string&gt;
。因此,每当您调用
GENSET
的构造函数时,您都会在class文字中传递第一个参数,代表Genset的数组
实例的声明类型(例如] .class 用于Genset&lt; string&gt;
)。请注意,您将无法获得一系列原始词,因为原始词不能用于类型变量。在构造函数内部,调用方法
cast
返回传递的对象
参数施放到由class
对象所示的类表示的类。在java.lang.reflect.Array
返回作为对象
以class表示的类型的数组返回,调用静态方法
对象作为第一个参数和由newInstance
int
指定的长度传递给了第二个参数。调用方法getComponentType
返回class
对象代表由class
class 对象表示该方法的组件类型(例如代码> string.class forstring []。class
,null
如果class
对象不表示数组)。最后一句话并不完全准确。调用
string []。class.getComponentType()
返回class
代表类String
的对象,但其类型是class&lt;? &gt;
,不是class&lt; string&gt;
,这就是为什么您不能做以下操作的原因。类
中的每种方法都返回class
对象。关于Joachim Sauer在有足够的声誉可以自己对其发表评论),该示例使用铸件对
t []
将导致警告,因为编译器不能保证在这种情况下键入安全性。编辑INGO的评论:
Here's how to use generics to get an array of precisely the type you’re looking for while preserving type safety (as opposed to the other answers, which will either give you back an
Object
array or result in warnings at compile time):That compiles without warnings, and as you can see in
main
, for whatever type you declare an instance ofGenSet
as, you can assigna
to an array of that type, and you can assign an element froma
to a variable of that type, meaning that the array and the values in the array are of the correct type.It works by using class literals as runtime type tokens, as discussed in the Java Tutorials. Class literals are treated by the compiler as instances of
java.lang.Class
. To use one, simply follow the name of a class with.class
. So,String.class
acts as aClass
object representing the classString
. This also works for interfaces, enums, any-dimensional arrays (e.g.String[].class
), primitives (e.g.int.class
), and the keywordvoid
(i.e.void.class
).Class
itself is generic (declared asClass<T>
, whereT
stands for the type that theClass
object is representing), meaning that the type ofString.class
isClass<String>
.So, whenever you call the constructor for
GenSet
, you pass in a class literal for the first argument representing an array of theGenSet
instance's declared type (e.g.String[].class
forGenSet<String>
). Note that you won't be able to get an array of primitives, since primitives can't be used for type variables.Inside the constructor, calling the method
cast
returns the passedObject
argument cast to the class represented by theClass
object on which the method was called. Calling the static methodnewInstance
injava.lang.reflect.Array
returns as anObject
an array of the type represented by theClass
object passed as the first argument and of the length specified by theint
passed as the second argument. Calling the methodgetComponentType
returns aClass
object representing the component type of the array represented by theClass
object on which the method was called (e.g.String.class
forString[].class
,null
if theClass
object doesn't represent an array).That last sentence isn't entirely accurate. Calling
String[].class.getComponentType()
returns aClass
object representing the classString
, but its type isClass<?>
, notClass<String>
, which is why you can't do something like the following.Same goes for every method in
Class
that returns aClass
object.Regarding Joachim Sauer's comment on this answer (I don't have enough reputation to comment on it myself), the example using the cast to
T[]
will result in a warning because the compiler can't guarantee type safety in that case.Edit regarding Ingo's comments:
这是唯一类型安全的答案:
This is the only answer that is type-safe:
要扩展到更多维度,只需添加
[]
的s和Dimension参数到newInstance()
(t
是类型参数,cls
是class&lt; t&gt;
,d1
通过d5
是整数):参见 有关详细信息。
To extend to more dimensions, just add
[]
's and dimension parameters tonewInstance()
(T
is a type parameter,cls
is aClass<T>
,d1
throughd5
are integers):See
Array.newInstance()
for details.有效java,第二版,项目25 ... 更喜欢列表而不是阵列
您的代码会产生未检查的警告(您可以用以下注释抑制它:
但是,最好使用列表而不是使用列表而不是数组。
在
This is covered in Chapter 5 (Generics) of Effective Java, 2nd Edition, item 25...Prefer lists to arrays
Your code will work, although it will generate an unchecked warning (which you could suppress with the following annotation:
However, it would probably be better to use a List instead of an Array.
There's an interesting discussion of this bug/feature on the OpenJDK project site.
在 Java 8 中,我们可以使用 lambda 或方法引用来创建通用数组。这类似于反射方法(传递
Class
),但这里我们不使用反射。例如,它由 <代码> A[] Stream.toArray(IntFunction)。
在 Java 8 之前,也可以使用匿名类来完成此操作,但比较麻烦。
In Java 8, we can do a kind of generic array creation using a lambda or method reference. This is similar to the reflective approach (which passes a
Class
), but here we aren't using reflection.For example, this is used by
<A> A[] Stream.toArray(IntFunction<A[]>)
.This could also be done pre-Java 8 using anonymous classes but it's more cumbersome.
您无需将类参数传递给构造函数。
尝试一下。
结果
:
You do not need to pass the Class argument to the constructor.
Try this.
and
result:
Java 泛型的工作原理是在编译时检查类型并插入适当的转换,但会删除编译文件中的类型。这使得泛型库可以被不理解泛型的代码使用(这是一个经过深思熟虑的设计决策),但这意味着您通常无法在运行时找出类型是什么。
公共 Stack(Class clazz,intcapacity) 构造函数要求您在运行时传递一个 Class 对象,这意味着类信息在运行时可用于编写以下代码:需要它。而
Class
形式意味着编译器将检查您传递的 Class 对象是否正是类型 T 的 Class 对象。不是 T 的子类,也不是 T 的超类,而是恰好 T 。这意味着您可以在构造函数中创建适当类型的数组对象,这意味着您存储在集合中的对象的类型将在将它们添加到集合中时进行类型检查
Java generics work by checking types at compile time and inserting appropriate casts, but erasing the types in the compiled files. This makes generic libraries usable by code which doesn't understand generics (which was a deliberate design decision) but which means you can't normally find out what the type is at run time.
The public
Stack(Class<T> clazz,int capacity)
constructor requires you to pass a Class object at run time, which means class information is available at runtime to code that needs it. And theClass<T>
form means that the compiler will check that the Class object you pass is precisely the Class object for type T. Not a subclass of T, not a superclass of T, but precisely T.This then means that you can create an array object of the appropriate type in your constructor, which means that the type of the objects you store in your collection will have their types checked at the point they are added to the collection.
泛型用于编译时的类型检查。因此,目的是检查
检查这个:
当你编写泛型类时,不要担心类型转换警告;使用时请担心。
Generics are used for type checking during compile time. Therefore, the purpose is to check
Check this:
Don't worry about typecasting warnings when you are writing a generic class; worry when you are using it.
这个解决方案怎么样?
它工作起来而且看起来简单得令人难以置信。有什么缺点吗?
What about this solution?
It works and looks too simple to be true. Is there any drawback?
该示例是使用Java反射来创建数组。通常不建议这样做,因为它不是typesafe。取而代之的是,您应该做的就是使用内部列表,然后完全避免数组。
The example is using Java reflection to create an array. Doing this is generally not recommended, since it isn't typesafe. Instead, what you should do is just use an internal List, and avoid the array at all.
传递值列表...
Passing a list of values...
还要查看此代码:
它将任何类型对象的列表转换为相同类型的数组。
Look also to this code:
It converts a list of any kind of object to an array of the same type.
我找到了一种对我有用的快速简便的方法。请注意,我仅在Java JDK 8上使用过此功能。我不知道它是否可以与以前的版本一起使用。
尽管我们无法实例化特定类型参数的通用数组,但我们可以将已创建的数组传递给通用类构造函数。
现在,我们可以创建类似的数组:
为了更具灵活性,您可以使用链接的列表,例如java.util.arlaylist类中的ArrayList和其他方法。
I have found a quick and easy way that works for me. Note that I have only used this on Java JDK 8. I don't know if it will work with previous versions.
Although we cannot instantiate a generic array of a specific type parameter, we can pass an already created array to a generic class constructor.
Now in main we can create the array like so:
For more flexibility with your arrays, you can use a linked list e.g., the ArrayList and other methods found in the Java.util.ArrayList class.
我制作了这个代码片段来反射性地实例化一个为简单的自动化测试实用程序传递的类。
请注意此段:
对于数组初始化,其中 Array.newInstance(数组的类,数组的大小)。类可以是基元 (int.class) 和对象 (Integer.class)。
BeanUtils 是 Spring 的一部分。
I made this code snippet to reflectively instantiate a class which is passed for a simple automated test utility.
Note this segment:
for array initiating where Array.newInstance(class of array, size of array). Class can be both primitive (int.class) and object (Integer.class).
BeanUtils is part of Spring.
其他人建议的强制转换对我不起作用,引发了非法转换的异常。
然而,这种隐式转换工作得很好:
其中 Item 是我定义的包含成员的类:
这样您就可以获得类型 K 的数组(如果该项目仅具有值)或您想要在类 Item 中定义的任何泛型类型。
The forced cast suggested by other people did not work for me, throwing an exception of illegal casting.
However, this implicit cast worked fine:
where Item is a class I defined containing the member:
This way you get an array of type K (if the item only has the value) or any generic type you want defined in the class Item.
实际上,一种更简单的方法是创建一个对象数组并将其转换为您想要的类型,如下例所示:
其中
SIZE
是常量,T
是类型标识符Actually an easier way to do so, is to create an array of objects and cast it to your desired type like the following example:
where
SIZE
is a constant andT
is a type identifier没有其他人回答您发布的示例中发生的情况的问题。
正如其他人所说,泛型在编译过程中被“删除”。因此,在运行时,泛型的实例不知道其组件类型是什么。这样做的原因是历史性的,Sun 希望在不破坏现有接口(源代码和二进制)的情况下添加泛型。
另一方面,数组确实在运行时知道它们的组件类型。
此示例通过让调用构造函数(确实知道类型)的代码传递一个参数来告诉类所需的类型来解决该问题。
因此,应用程序将使用类似的内容构造类
,并且构造函数现在知道(在运行时)组件类型是什么,并且可以使用该信息通过反射 API 构造数组。
最后我们进行了类型转换,因为编译器无法知道 Array#newInstance() 返回的数组是正确的类型(即使我们知道)。
这种风格有点丑陋,但有时它可能是创建泛型类型的最不坏的解决方案,这些泛型类型确实需要在运行时了解其组件类型,无论出于何种原因(创建数组或创建其组件类型的实例等)。
No one else has answered the question of what is going on in the example you posted.
As others have said generics are "erased" during compilation. So at runtime an instance of a generic doesn't know what its component type is. The reason for this is historical, Sun wanted to add generics without breaking the existing interface (both source and binary).
Arrays on the other hand do know their component type at runtime.
This example works around the problem by having the code that calls the constructor (which does know the type) pass a parameter telling the class the required type.
So the application would construct the class with something like
and the constructor now knows (at runtime) what the component type is and can use that information to construct the array through the reflection API.
Finally we have a type cast because the compiler has no way of knowing that the array returned by
Array#newInstance()
is the correct type (even though we know).This style is a bit ugly but it can sometimes be the least bad solution to creating generic types that do need to know their component type at runtime for whatever reason (creating arrays, or creating instances of their component type, etc.).
我找到了解决这个问题的一种方法。
下面的行抛出通用数组创建错误:
但是,如果我将
List
封装在一个单独的类中,它就可以工作。您可以通过 getter 公开 PersonList 类中的人员。下面的行将为您提供一个数组,其中每个元素都有一个
List
。换句话说,List
数组。我正在编写的一些代码中需要类似的东西,这就是我所做的让它工作。到目前为止还没有任何问题。
I found a sort of a workaround to this problem.
The line below throws generic array creation error:
However, if I encapsulate
List<Person>
in a separate class, it works.You can expose people in the class PersonList through a getter. The line below will give you an array, that has a
List<Person>
in every element. In other words array ofList<Person>
.I needed something like this in some code I was working on and this is what I did to get it to work. So far there aren't any problems.
Java中不允许使用通用数组的创建,但是您可以这样做:
Generic array creation is disallowed in Java, but you can do it like:
您可以创建一个对象数组并将其强制转换为 E 。是的,这不是很干净的方法,但它至少应该有效。
You could create an Object array and cast it to E everywhere. Yeah, it's not very clean way to do it but it should at least work.
也许与这个问题无关,但是当我收到“
通用数组创建
”错误时,我发现以下工作(并为我工作)与
@SuppressWarnings({"unchecked"} )
:Maybe unrelated to this question but while I was getting the "
generic array creation
" error for usingI find out the following works (and worked for me) with
@SuppressWarnings({"unchecked"})
:你可以使用强制转换:
You could use a cast:
如果您确实想包装固定大小的通用数组,您将有一个方法将数据添加到该数组,因此您可以正确初始化数组,执行如下操作:
在这种情况下,您使用 java.lang.reflect.Array .newInstance来创建数组,它不会是一个Object[],而是一个真正的T[]。
您不必担心它不是最终的,因为它是在您的班级内部管理的。
请注意,push() 上需要一个非空对象才能获取要使用的类型,因此我添加了对推送的数据的检查并在那里抛出异常。
但这仍然有些毫无意义:您通过推送存储数据,并且方法的签名保证只有 T 元素会进入。因此数组是 Object[] 还是 T[] 或多或少无关紧要。
If you really want to wrap a generic array of fixed size you will have a method to add data to that array, hence you can initialize properly the array there doing something like this:
in this case you use a java.lang.reflect.Array.newInstance to create the array, and it will not be an Object[], but a real T[].
You should not worry of it not being final, since it is managed inside your class.
Note that you need a non null object on the push() to be able to get the type to use, so I added a check on the data you push and throw an exception there.
Still this is somewhat pointless: you store data via push and it is the signature of the method that guarantees only T elements will enter. So it is more or less irrelevant that the array is Object[] or T[].
我想知道此代码是否会创建有效的通用数组?
如果您需要的大小且小的大小是简单地将所需数量的“ null” s送入ZeroArray命令,则可能是创建这样的数组的另一种方法?
尽管显然这不像使用CreateArray代码那样通用。
I'm wondering if this code would create an effective generic array?
Perhaps an alternate way of creating such an array, if the size you required was known and small, would be to simply feed the required number of "null"s into the zeroArray command?
Though obviously this isn't as versatile as using the createArray code.
实际上,我找到了一个非常独特的解决方案来绕过无法启动通用数组的问题。您必须创建一个接受通用变量 T 的类,如下所示:
然后在您的数组类中,使其像这样开始:
启动一个新的 Generic Invoker[] 会导致未经检查的问题但实际上不应该有任何问题。
要从数组中获取数据,您应该像这样调用 array[i].variable:
其余的,例如调整数组大小,可以使用 Arrays.copyOf() 来完成,如下所示:
并且可以像这样添加 add 函数:
I actually found a pretty unique solution to bypass the inability to initiate a generic array. You have to create a class that takes in the generic variable T like so:
And then in your array class, just have it start like so:
starting a
new Generic Invoker[]
will cause an issue with unchecked but there shouldn't actually be any issues.To get from the array you should call the array[i].variable like so:
The rest, such as resizing the array can be done with Arrays.copyOf() like so:
And the add function can be added like so:
数组不支持仿制药(因为它是另一种类型的数据),但是如果您不需要铸造,则可以在创建时使用不确定的仿制药。顺便说一句,它比使用反射要好:
因此,即使没有
未经检查的类型
警告,我们也将获得法律通用数组。Arrays do not support generics (because it's another type of data), but you can use undeterminate generics while its creation if you don't need casting. By the way, it's better than using reflection:
So now we are getting the legal generics array even without
Unchecked type
warning.该语法
方式填充。
创建一个空引用数组,以类型安全的
The syntax
creates an array of null references, to be filled as
which is type safe.
一个简单但混乱的解决方法是在主类中嵌套第二个“holder”类,并使用它来保存数据。
An easy, albeit messy workaround to this would be to nest a second "holder" class inside of your main class, and use it to hold your data.