由于Java泛型的实现,您不能拥有这样的代码:
public class GenSet{ private E a[]; public GenSet() { a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation } }
如何在保持类型安全的同时实现这一点?
我在Java论坛上看到了这样的解决方案:
import java.lang.reflect.Array; class Stack{ public Stack(Class clazz, int capacity) { array = (T[])Array.newInstance(clazz, capacity); } private final T[] array; }
但我真的不知道发生了什么.
我必须回答一个问题:你的GenSet
"已检查"还是"未选中"?那是什么意思?
检查:强打字.GenSet
明确地知道它包含什么类型的对象(即它的构造函数是用Class
参数显式调用的,当方法传递非类型的参数时,方法会抛出异常E
.参见Collections.checkedCollection
.
- >在这种情况下,你应该写:
public class GenSet{ private E[] a; public GenSet(Class 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{ 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 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中泛型的已知且有意识的弱点:它是使用擦除实现的,因此"泛型"类不知道它们在运行时创建的类型参数,因此无法提供类型 - 安全,除非实施一些显式机制(类型检查).
你可以这样做:
E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];
这是在Effective Java中实现泛型集合的建议方法之一; 项目26.没有类型错误,不需要重复强制转换数组. 但是,这会触发警告,因为它有潜在危险,应谨慎使用.正如评论中所详述的那样,Object[]
现在伪装成我们的E[]
类型,ClassCastException
如果使用不安全,可能会导致意外错误.
根据经验,只要在内部使用强制转换数组(例如,支持数据结构),并且不返回或暴露给客户端代码,此行为就是安全的.如果您需要将泛型类型的数组返回给其他代码,Array
您提到的反射类是正确的方法.
值得一提的是,只要有可能,List
如果你使用泛型,那么使用s而不是数组会更快乐.当然有时你没有选择,但使用集合框架更加健壮.
下面是如何使用泛型来获得一个正在寻找的类型的数组,同时保留类型安全性(与其他答案相反,后者将返回一个Object
数组或在编译时导致警告):
import java.lang.reflect.Array; public class GenSet{ private E[] a; public GenSet(Class clazz, int length) { a = clazz.cast(Array.newInstance(clazz.getComponentType(), length)); } public static void main(String[] args) { GenSet foo = new GenSet (String[].class, 1); String[] bar = foo.a; foo.a[0] = "xyzzy"; String baz = foo.a[0]; } }
在没有警告的情况下进行编译,正如您所看到的main
,对于您声明GenSet
as 的实例的任何类型,您可以分配a
给该类型的数组,并且可以将元素分配给a
该类型的变量,这意味着该数组并且数组中的值具有正确的类型.
它的工作原理是使用类文字作为运行时类型标记,如Java教程中所述.编译器将类文字视为实例java.lang.Class
.要使用一个,只需按照类的名称.class
.因此,String.class
充当Class
表示类的对象String
.这也适用于接口,枚举,任何维数组(例如String[].class
),基元(例如int.class
)和关键字void
(即void.class
).
Class
本身是通用的(声明为Class
,T
代表Class
对象所代表的类型),意味着类型String.class
是Class
.
因此,无论何时调用构造函数,都会GenSet
为第一个参数传递一个类文字,表示GenSet
实例声明类型的数组(例如String[].class
for GenSet
).请注意,您将无法获取基元数组,因为基元不能用于类型变量.
在构造函数内部,调用该方法cast
将传递的Object
参数强制转换为由调用该方法的Class
对象表示的类.调用静态方法newInstance
在java.lang.reflect.Array
返回作为Object
由所表示的类型的数组Class
作为第一个参数,并通过指定的长度的传递的对象int
通过作为第二个参数.调用该方法getComponentType
返回一个Class
表示组件类型由所表示的所述阵列的物体Class
在其上的方法被调用(例如对象String.class
为String[].class
,null
如果Class
对象不表示阵列).
最后一句话并不完全准确.调用String[].class.getComponentType()
返回一个Class
表示该类的对象String
,但它的类型Class>
不是Class
,这就是为什么你不能做以下的事情.
String foo = String[].class.getComponentType().cast("bar"); // won't compile
Class
返回Class
对象的每个方法都是如此.
关于Joachim Sauer对此答案的评论(我自己没有足够的声誉对其进行评论),使用强制转换的示例T[]
将导致警告,因为在这种情况下编译器无法保证类型安全.
关于Ingo的评论编辑:
public staticT[] newArray(Class type, int size) { return type.cast(Array.newInstance(type.getComponentType(), size)); }
这是唯一类型安全的答案
E[] a; a = newArray(size); @SafeVarargs staticE[] newArray(int length, E... array) { return Arrays.copyOf(array, length); }
要扩展到更多维度,只需添加[]
's和维度参数newInstance()
(T
是一个类型参数,cls
是一个Class
,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 8中,我们可以使用lambda或方法引用来创建一种通用数组.这类似于反射方法(通过a Class
),但这里我们没有使用反射.
@FunctionalInterface interface ArraySupplier{ E[] get(int length); } class GenericSet { private final ArraySupplier supplier; private E[] array; GenericSet(ArraySupplier supplier) { this.supplier = supplier; this.array = supplier.get(10); } public static void main(String[] args) { GenericSet ofString = new GenericSet<>(String[]::new); GenericSet ofDouble = new GenericSet<>(Double[]::new); } }
例如,这被使用 A[] Stream.toArray(IntFunction)
.
这可能也可以做预先的Java 8中使用匿名类,但它更繁琐.
这将在Effective Java的第5章(泛型),第2版,第 25项中介绍... 首选列表到数组
您的代码将起作用,但它会生成未经检查的警告(您可以使用以下注释来抑制:
@SuppressWarnings({"unchecked"})
但是,使用List而不是Array可能会更好.
在OpenJDK项目网站上有一个关于这个bug /功能的有趣讨论.
Java泛型通过在编译时检查类型并插入适当的强制转换来工作,但是擦除已编译文件中的类型.这使得通用库可以被不能理解泛型的代码使用(这是一个深思熟虑的设计决策),但这意味着你通常无法在运行时找出类型是什么.
公共Stack(Class
构造函数需要在运行时,这意味着类信息传递类对象是可以在运行时需要它的代码.并且Class
表单意味着编译器将检查您传递的Class对象是否是类型T的Class对象.不是T的子类,不是T的超类,而是T的超类.
这意味着您可以在构造函数中创建相应类型的数组对象,这意味着您在集合中存储的对象类型将在添加到集合中时检查其类型.
嗨虽然线程已经死了,但我想提请你注意这个:
泛型用于在编译期间进行类型检查:
因此,目的是检查您所需要的是什么.
您返回的是消费者需要的东西.
检查一下:
在编写泛型类时,不要担心类型转换警告.在使用它时担心.
这个解决方案怎么样?
@SafeVarargs public staticT[] toGenericArray(T ... elems) { return elems; }
它的工作原理看起来太简单了.有什么缺点吗?
您无需将Class参数传递给构造函数。尝试这个。
public class GenSet{ 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 + "]"; } }
和
GenSetintSet = new GenSet<>(3); System.out.println(intSet); System.out.println(new GenSet (2));
结果:
GenSet of java.lang.Integer[3] GenSet of java.lang.String[2]
还要看这段代码:
public staticT[] toArray(final List 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上使用过它.我不知道它是否适用于以前的版本.
虽然我们无法实例化特定类型参数的泛型数组,但我们可以将已创建的数组传递给泛型类构造函数.
class GenArray{ private T theArray[]; // reference array // ... GenArray(T[] arr) { theArray = arr; } // Do whatever with the array... }
现在在main中我们可以像这样创建数组:
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= new Character<>(ar); // create the generic Array // ... } }
为了更灵活地使用数组,您可以使用链接列表,例如.ArrayList和Java.util.ArrayList类中的其他方法.