为什么用Java在数组中删除泛型?

Pap*_*sar 2 java arrays generics

我知道,泛型类型信息是在Java编译时删除的,因此不能创建泛型类型的数组(因为那样在运行时将无法有效地强制插入数组中的类型)。

但是为什么不例外呢?为什么不只为数组保留通用类型信息(并且仅为数组保留)?

这背后的设计决定是什么?我认为这将使生活更轻松,有可能做到这一点。

T[] genericArray = new T[10];
Run Code Online (Sandbox Code Playgroud)

Ran*_*niz 5

简短答案:

这是因为泛型是元数据,可帮助编译器帮助您捕获类型错误,并且所有内容都被编译为使用最低公分母(通常是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