由于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;
class Stack<T> {
public Stack(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}
private final T[] array;
}
但我真的不知道发生了什么 .
29 回答
你可以使用一个演员:
我不得不问一个问题:你的
GenSet
"checked"或"unchecked"?那是什么意思?Checked :强打字 .
GenSet
明确知道它包含的对象类型(即它的构造函数是使用Class<E>
参数显式调用的,并且当方法传递非_1177416_类型的参数时,方法将抛出异常 . 请参阅Collections.checkedCollection .Unchecked :弱打字 . 实际上没有对作为参数传递的任何对象进行类型检查 .
请注意,数组的组件类型应为type参数的erasure:
所有这些都源于Java中已知且有意识的泛型弱点:它是使用擦除实现的,因此“泛型”类不知道它们在运行时创建的类型参数,因此无法提供类型 - 安全,除非实施一些显式机制(类型检查) .
试试这个 .
要扩展到更多维度,只需将
[]
和维度参数添加到newInstance()
(T
是类型参数,cls
是Class<T>
,d1
到d5
是整数):有关详细信息,请参阅Array.newInstance() .
我找到了一种适合我的快捷方式 . 请注意,我只在Java JDK 8上使用过它 . 我不知道它是否适用于以前的版本 .
虽然我们无法实例化特定类型参数的泛型数组,但我们可以将已创建的数组传递给泛型类构造函数 .
现在在main中我们可以像这样创建数组:
为了更灵活地使用数组,您可以使用链接列表,例如 . ArrayList和Java.util.ArrayList类中的其他方法 .
我想知道这段代码是否会创建一个有效的通用数组?
编辑:也许创建这样一个数组的另一种方法,如果你需要的大小已知且很小,那么只需将所需数量的“null”提供给zeroArray命令?
虽然这显然不如使用createArray代码那样通用 .
这是唯一类型安全的答案
一个简单但易混乱的解决方法是在主类中嵌套第二个“holder”类,并使用它来保存数据 .
我实际上找到了一个非常独特的解决方案来绕过无法启动通用数组 . 你要做的是创建一个类,它接受泛型变量T,如下所示:
然后在你的数组类中,让它像这样开始:
启动
new Generic Invoker[]
将导致未选中的问题,但实际上不应该有任何问题 .要从数组中获取,您应该像这样调用数组[i] .variable:
其余的,比如调整数组的大小可以使用Arrays.copyOf()来完成,如下所示:
添加功能可以像这样添加:
嗨虽然线程已经死了,但我想提请你注意这个:
泛型用于在编译期间进行类型检查:
因此,目的是检查您所需要的是什么 .
您返回的是消费者需要的东西 .
检查一下:
在编写泛型类时,不要担心类型转换警告 . 在使用它时担心 .
java中不允许使用通用数组创建,但您可以这样做
其他人建议的强迫演员对我不起作用,除了非法演员之外 .
但是,这个隐式转换工作正常:
其中Item是我定义的包含成员的类:
这样,您将获得类型为K的数组(如果项目仅具有值)或您希望在类Item中定义的任何泛型类型 .
在Java 8中,我们可以使用lambda或方法引用来创建一种通用数组 . 这类似于反射方法(传递
Class
),但这里我们没有使用反射 .例如,<A> A[] Stream.toArray(IntFunction<A[]>)使用它 .
这也可以使用匿名类在Java 8之前完成,但它更麻烦 .
下面是如何使用泛型来获得一个正在寻找的类型的数组,同时保留类型安全性(与其他答案相反,它将返回一个
Object
数组或在编译时导致警告):这可以在没有警告的情况下编译,正如您在
main
中所看到的,对于您声明GenSet
实例的任何类型,您可以将a
分配给该类型的数组,并且您可以将a
中的元素分配给该类型的变量,这意味着数组和数组中的值具有正确的类型 .它的工作原理是使用类文字作为运行时类型标记,如Java Tutorials中所述 . 编译器将类文字视为
java.lang.Class
的实例 . 要使用一个,只需使用.class
跟随类的名称即可 . 因此,String.class
充当表示类String
的Class
对象 . 这也适用于接口,枚举,任何维数组(例如String[].class
),基元(例如int.class
)和关键字void
(即void.class
) .Class
本身是通用的(声明为Class<T>
,其中T
代表Class
对象所代表的类型),这意味着String.class
的类型是Class<String>
.因此,每当您调用
GenSet
的构造函数时,都会为第一个参数传递一个类文字,表示GenSet
实例的声明类型的数组(例如String[].class
表示GenSet<String>
) . 请注意,您赢得了't be able to get an array of primitives, since primitives can'用于类型变量 .在构造函数内部,调用方法
cast
将返回传递的Object
参数强制转换为由调用该方法的Class
对象表示的类 . 在java.lang.reflect.Array
中调用静态方法newInstance
将返回Object
,该数组由Class
对象表示,该对象作为第一个参数传递,并且int
指定的长度作为第二个参数传递 . 调用方法getComponentType
会返回一个Class
对象,该对象表示由调用该方法的Class
对象表示的数组的组件类型(例如String.class
表示String[].class
,如果Class
对象不表示数组,则为null
) .最后一句话并不完全准确 . 调用
String[].class.getComponentType()
会返回表示类String
的Class
对象,但其类型为Class<?>
,而不是Class<String>
,这就是为什么您不能执行以下操作的原因 .同样适用于
Class
中返回Class
对象的每个方法 .关于Joachim Sauer对this answer的评论(我自己没有足够的声誉对其进行评论),使用强制转换为
T[]
的示例将导致警告,因为在这种情况下编译器无法保证类型安全 .关于Ingo的评论编辑:
这个解决方案怎么样?
它的工作原理看起来太简单了 . 有什么缺点吗?
另请参阅此代码:
它将任何类型对象的列表转换为相同类型的数组 .
我创建了这个代码片段,以反射方式实例化一个为简单的自动化测试实用程序传递的类 .
请注意此细分:
对于数组启动 Array.newInstance(class of array, size of array) . 类可以是原始(int.class)和对象(Integer.class) .
BeanUtils是Spring的一部分 .
这包含在Effective Java, 2nd Edition的第5章(泛型)中,第25项...... Prefer lists to arrays
您的代码将起作用,但它会生成未经检查的警告(您可以使用以下注释来抑制:
但是,使用List而不是Array可能会更好 .
在the OpenJDK project site上有一个关于这个bug /功能的有趣讨论 .
我发现了一种解决这个问题的方法 .
下面的行抛出了泛型数组创建错误
但是,如果我将
List<Person>
封装在一个单独的类中,它就可以工作 .您可以通过getter公开PersonList类中的人员 . 下面的行将为您提供一个数组,每个元素都有一个
List<Person>
. 换句话说List<Person>
的数组 .在我正在研究的一些代码中,我需要这样的东西,这就是我为了让它工作而做的 . 到目前为止没有问题 .
您可以创建一个Object数组并将其转换为E到处 . 是的,它不是很干净的方式,但至少应该工作 .
传递值列表......
您不需要将Class参数传递给构造函数 . 试试这个 .
和
结果:
也许与这个问题无关但我在使用时遇到“
generic array creation
”错误我用
@SuppressWarnings({"unchecked"})
找到了以下作品(并为我工作):你可以这样做:
这是在Effective Java中实现泛型集合的建议方法之一;项目26.没有类型错误,不需要重复投射数组 . 但是,这会触发警告,因为它有潜在危险,应谨慎使用 . 正如评论中所详述的那样,这个
Object[]
现在伪装成我们的E[]
类型,并且如果使用不安全会导致意外错误或ClassCastException
.根据经验,只要在内部使用强制转换数组(例如,支持数据结构),并且不返回或暴露给客户端代码,此行为就是安全的 . 如果您需要将泛型类型的数组返回给其他代码,您提到的反射
Array
类是正确的方法 .值得一提的是,只要有可能,如果你有选择的话,你可以更愉快地使用
List
而不是数组,但使用集合框架会更加健壮 .实际上,更简单的方法是创建一个对象数组并将其转换为您想要的类型,如下例所示:
其中
SIZE
是常量,T
是类型标识符没有人回答你发布的例子中发生了什么的问题 .
正如其他人所说,在编译过程中,泛型被“擦除” . 因此,在运行时,泛型的实例不知道其组件类型是什么 . 其原因是历史性的,Sun希望在不破坏现有接口(源和二进制)的情况下添加泛型 .
另一方面,数组在运行时确实知道它们的组件类型 .
这个例子解决了这个问题,让调用构造函数的代码(确实知道类型)传递一个参数,告诉类所需的类型 .
因此,应用程序将使用类似的东西来构造类
并且构造函数现在知道(在运行时)组件类型是什么,并且可以使用该信息通过反射API构造数组 .
最后我们有一个类型转换,因为编译器无法知道
Array#newInstance()
返回的数组是正确的类型(即使我们知道) .这种风格有点难看,但它有时可能是创建通用类型的最不好的解决方案,这些类型需要在运行时因任何原因(创建数组或创建其组件类型的实例等)知道它们的组件类型 .
Java泛型通过在编译时检查类型并插入适当的强制转换来工作,但是擦除已编译文件中的类型 . 这使得通用库可以被代码使用,而这些代码通常不会在运行时找出类型 .
public
Stack(Class<T> clazz,int capacity)
构造函数要求您在运行时传递Class对象,这意味着类信息在运行时可用于需要它的代码 .Class<T>
表单意味着编译器将检查您传递的Class对象是否是类型T的Class对象 . 不是T的子类,不是T的超类,而是T的超类 .这意味着您可以在构造函数中创建相应类型的数组对象,这意味着您在集合中存储的对象类型将在它们添加到集合时检查其类型 .
该示例使用Java反射来创建数组 . 通常不建议这样做,因为它不是类型安全的 . 相反,你应该做的只是使用内部List,并完全避免使用数组 .