【问题标题】:Why are generics erased in arrays in Java?为什么泛型在 Java 中的数组中被擦除?
【发布时间】:2015-07-10 07:54:16
【问题描述】:

我知道泛型类型信息在 Java 编译时会被删除,因此无法创建泛型类型数组(因为在运行时无法有效地强制插入数组中的类型)。
但为什么不破例呢?为什么不只保留数组的通用类型信息(并且只保留它们)?

这背后的设计决策是什么?在我看来,这会让生活更轻松,这样做是可能的。

T[] genericArray = new T[10];

【问题讨论】:

  • 可能向后兼容?
  • 这可能是重复的。
  • @BendeguzNagy 我不确定在数组的情况下使用T[] genericArray = new T[10]; 与使用Object[] genericArray = new Object[10] 会有什么优势。类型参数几乎没用(除非您期望一旦将元素添加到T[] genericArray ,编译器应该推断类型并且不允许您添加不属于插入的第一个元素类型的元素?当然不是可能而且要求太多)

标签: java arrays generics


【解决方案1】:

简答:

这是因为泛型是帮助编译器帮助你捕捉类型的元数据 错误,一切都被编译为使用最低公分母 (通常是Object)和类型转换。这不是用数组完成的,因为数组 是他们自己的班级。 IE。一个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);
    }

}

打印出来:

public static void Generics.print(java.lang.Object)
public static void Generics.printNumber(java.lang.Number)

所以我们可以看到,当它被编译时,它被编译为分别适用于 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));

如果你尝试一下,你会看到它打印出来

foo
10

因此,通过向下/向上转换,我们将 ArrayList&lt;String&gt; 转换为 ArrayList&lt;Object&gt; - 如果 ArrayList 实际上将其内容存储在 String[] 而不是 Object[] 类型的数组中,这是不可能的.

注意尝试做

System.out.println(list.get(0));
System.out.println(list.get(1));

将产生ClassCastException。这暗示了究竟是什么 编译器会。

看下面的代码:

public static void doThingsWithList() {
    ArrayList<String> list = new ArrayList<>();
    list.add("");
    String s = list.get(0);
    print(s);
}

编译后变成这个字节码:

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

正如您在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;
}

进入

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" });

将是错误的,因为第一个数组的类为 Object[],第二个数组的类为 String[]。

当然,Java 可以更改为所有数组的类型为Object[],并且所有访问都将使用强制类型转换,就像使用泛型时一样。但这会破坏向后兼容性,而使用泛型则不会,因为 ArrayList&lt;String&gt; 与 ArrayList 具有相同的类。

【讨论】:

    【解决方案2】:

    new T[10]; 这样的构造的问题在于,T 可以是任何东西,包括 Void(它实际上是一个不可实例化的类,会产生编译错误)。

    如果你仔细想想,类型是一种语言结构,因此应该在编译时而不是在运行时发挥它的魔力。在某些情况下,拥有运行时类型信息并且某些语言实际实现它是有意义的,但您是否需要它并且是否是“好东西™”还有待商榷。

    关于类型擦除的一些有用信息:https://stackoverflow.com/a/21843984/1417546

    【讨论】:

    • Void 的数组没有问题,即使在运行时也是如此。这只是另一种类型:Void[] a = new Void[1]; 该类型有单个成员 null。
    • 当我说不可实例化时,我指的是你不能实例化Void 对象(new Void() 而不是new Void[],Voids 的数组实际上是可以接受的)。跨度>
    • 但是我在回答中澄清了这个概念,并且我错误地指出它产生了运行时错误,它实际上产生了编译时错误(the constructor is not visible)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    相关资源
    最近更新 更多