为什么在Java中数组中删除泛型?
我知道在Java中,泛型类型信息在编译时会被擦除,因此无法创建泛型类型的数组(因为在运行时无法有效地强制执行插入数组中的类型)。为什么在Java中数组中删除泛型?,java,arrays,generics,Java,Arrays,Generics,我知道在Java中,泛型类型信息在编译时会被擦除,因此无法创建泛型类型的数组(因为在运行时无法有效地强制执行插入数组中的类型)。 但为什么不破例呢?为什么不保留数组的泛型类型信息(并且只保留它们的泛型类型信息)? 这背后的设计决定是什么?在我看来,这将使生活更容易,这是可能的 T[] genericArray = new T[10]; 类似于newt[10]的构造问题是指T可以是任何东西,包括Void(它实际上是一个不可实例化的类,会生成编译错误) 仔细想想,类型是一种语言构造,因此应该在编
但为什么不破例呢?为什么不保留数组的泛型类型信息(并且只保留它们的泛型类型信息)?
这背后的设计决定是什么?在我看来,这将使生活更容易,这是可能的
T[] genericArray = new T[10];
类似于
newt[10]的构造问题
是指T
可以是任何东西,包括Void
(它实际上是一个不可实例化的类,会生成编译错误)
仔细想想,类型是一种语言构造,因此应该在编译时而不是在运行时发挥作用。在某些情况下,拥有运行时类型信息是有意义的,并且某些语言实际实现了它,但是您是否需要它以及它是否是一件“好事”还存在争议™".
有关类型擦除的一些有用信息:简短回答: 这是因为泛型是帮助编译器捕获类型的元数据 错误,所有内容都被编译为使用最小公分母 (通常是
对象
)和类型转换。这不适用于数组,因为数组
是它们自己的类。即ArrayList
和ArrayList
两者都有classArrayList
,但是String
数组有classString[]
和
Number
具有类Number[]
长答案:
编译时,所有使用泛型的东西都将使用最少的
公共分母(通常是对象
)。这可以通过
以下代码:
public class Generics {
public static <T> void print(T what) {
System.out.println(what);
}
public static <T extends Number> void printNumber(T what) {
System.out.println(what);
}
public static void main(String[] args) {
Arrays.stream(Generics.class.getDeclaredMethods())
.filter(m -> m.getName().startsWith("print"))
.forEach(Generics::print);
}
}
public static void doThingsWithList() {
ArrayList<String> list = new ArrayList<>();
list.add("");
String s = list.get(0);
print(s);
}
所以我们可以看到,当它被编译时,它被编译为分别作用于对象
和数字
的方法
这就是像这样的东西将编译并运行的原因:
ArrayList<String> list = new ArrayList<>();
list.add("foo");
ArrayList<Object> list2 = (ArrayList<Object>)(Object)list;
list2.add(Integer.valueOf(10));
System.out.println(list2.get(0));
System.out.println(list2.get(1));
因此,通过向下/向上转换,我们将ArrayList
转换为ArrayList
——如果ArrayList实际将其内容存储在String[]
类型的数组中,而不是Object[]
,这是不可能的
请注意,尝试
System.out.println(list.get(0));
System.out.println(list.get(1));
将导致一个ClassCastException
。这将提示
编译器会
请看以下代码:
public class Generics {
public static <T> void print(T what) {
System.out.println(what);
}
public static <T extends Number> void printNumber(T what) {
System.out.println(what);
}
public static void main(String[] args) {
Arrays.stream(Generics.class.getDeclaredMethods())
.filter(m -> m.getName().startsWith("print"))
.forEach(Generics::print);
}
}
public static void doThingsWithList() {
ArrayList<String> list = new ArrayList<>();
list.add("");
String s = list.get(0);
print(s);
}
进入
将为false,因为第一个数组将具有classObject[]
,第二个数组将具有classString[]
当然,可以更改Java,使所有数组的类型都是
Object[]
所有访问都将使用强制类型转换,就像使用泛型一样。但这会破坏向后兼容性,而使用泛型则不会,因为ArrayList
可能与ArrayList具有相同的类向后兼容性?这可能是重复的。@BendeguzNagy我不确定您会有什么优势在数组中使用T[]genericArray=new T[10];
vs使用Object[]genericArray=new Object[10]
。类型参数几乎毫无用处(除非您希望在向T[]添加元素后genericArray
,编译器应该推断类型,不允许您添加与插入的第一个元素类型不同的元素?当然,这不可能,而且要求太高)即使在运行时,Void
数组也没有问题。它只是另一种类型:Void[]a=new Void[1]
该类型具有单个成员null
。当我说不可实例化时,我指的是不能实例化Void
对象(new Void()
而不是new Void[]
,实际上可以接受Void
的数组)但是,我在回答中澄清了这个概念,并且错误地指出它生成了一个运行时错误,实际上它生成了一个编译时错误(构造函数不可见
)。
public static void doThingsWithList();
Code:
0: new #11 // class java/util/ArrayList
3: dup
4: invokespecial #12 // Method java/util/ArrayList."<init>":()V
7: astore_0
8: aload_0
9: ldc #13 // String
11: invokevirtual #14 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
14: pop
15: aload_0
16: iconst_0
17: invokevirtual #15 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
20: checkcast #16 // class java/lang/String
23: astore_1
24: aload_1
25: invokestatic #17 // Method print:(Ljava/lang/Object;)V
28: return
public <T> T[] addToNewArrayAndPrint(T item) {
T[] array = new T[10];
array[0] = item;
System.out.println(array[0]);
return array;
}
public <T> T[] addToNewArrayAndPrint(T item) {
Object[] array = new Object[1];
array[0] = item;
System.out.println((T) array[0]);
return array;
}
Arrays.equals(addToNewArray("foo"), new String[]{ "foo" });