Pap*_*sar 2 java arrays generics
我知道,泛型类型信息是在Java编译时删除的,因此不能创建泛型类型的数组(因为那样在运行时将无法有效地强制插入数组中的类型)。
但是为什么不例外呢?为什么不只为数组保留通用类型信息(并且仅为数组保留)?
这背后的设计决定是什么?我认为这将使生活更轻松,有可能做到这一点。
T[] genericArray = new T[10];
Run Code Online (Sandbox Code Playgroud)
简短答案:
这是因为泛型是元数据,可帮助编译器帮助您捕获类型错误,并且所有内容都被编译为使用最低公分母(通常是Object)和类型强制转换。由于数组是它们自己的类,因此数组无法做到这一点。即,ArrayList<String>和一个ArrayList<Number>
都具有类ArrayList,但阵列String具有类String[]和阵列
Number具有类Number[]。
长答案:
编译后,所有使用泛型的东西都将使用最小公分母(通常是Object)。以下代码演示了这一点:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
打印:
public static void Generics.print(java.lang.Object)
public static void Generics.printNumber(java.lang.Number)
Run Code Online (Sandbox Code Playgroud)
因此,我们可以看到编译时将其编译为分别在Object和上运行的方法Number。
这就是这样的原因将编译并运行的原因:
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));
Run Code Online (Sandbox Code Playgroud)
如果尝试,您会看到它可以打印
foo
10
Run Code Online (Sandbox Code Playgroud)
因此,通过上下ArrayList<String>转换,我们将我们变成了ArrayList<Object>-,如果ArrayList实际上将其内容存储在type数组中String[]而不是上,这是不可能的Object[]。
请注意,尝试做
System.out.println(list.get(0));
System.out.println(list.get(1));
Run Code Online (Sandbox Code Playgroud)
将导致ClassCastException。这暗示了编译器的功能。
看下面的代码:
public static void doThingsWithList() {
ArrayList<String> list = new ArrayList<>();
list.add("");
String s = list.get(0);
print(s);
}
Run Code Online (Sandbox Code Playgroud)
编译后,将其转换为以下字节码:
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
Run Code Online (Sandbox Code Playgroud)
如您所见,实际上20来自的结果ArrayList.get被强制转换为String。
因此,泛型只是语法糖,可以转变为自动类型转换,并具有额外的好处,即编译器可以使用此语法糖来检测ClassCastException在运行时生成的代码。
现在,为什么编译器不能为String[]和做相同的事情Object[]?难道只是转身
public <T> T[] addToNewArrayAndPrint(T item) {
T[] array = new T[10];
array[0] = item;
System.out.println(array[0]);
return array;
}
Run Code Online (Sandbox Code Playgroud)
进入
public <T> T[] addToNewArrayAndPrint(T item) {
Object[] array = new Object[1];
array[0] = item;
System.out.println((T) array[0]);
return array;
}
Run Code Online (Sandbox Code Playgroud)
?
不。因为那意味着
Arrays.equals(addToNewArray("foo"), new String[]{ "foo" });
Run Code Online (Sandbox Code Playgroud)
将是错误的,因为第一个数组将具有class Object[],第二个数组将具有class String[]。
当然,可以对Java进行更改,以便所有数组都是类型Object[],并且所有访问都将使用强制类型转换,就像使用泛型时一样。但这会破坏向后兼容性,而使用Generics则不会,因为an与ArrayList<String>拥有相同的类ArrayList。
| 归档时间: |
|
| 查看次数: |
122 次 |
| 最近记录: |