java 泛型语法如何帮助避免类型转换?

How does java generics syntax help avoid type casting?

下面是代码,

import java.util.List;
import java.util.ArrayList;


public class Dummy {
    public static void main(String[] args) {
        List<String> lst = new  ArrayList<String>();
        lst.add("a string");
        lst.add("another string");
        String s = lst.get(0);
    } //end main
}

调用构造函数 new ArrayList<String>(); 时,会创建 Object 类型的数组。

..

lst 持有 Object[0] 个数组。

因此,如果 Object 类型的数组由构造函数创建,javac 如何在该语句 String s = lst.get(0); 中看不到类型转换问题,尽管在调用时使用了通用语法构造函数?

从 class 更远的地方:

@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

@SuppressWarnings 告诉编译器您确信您正在执行未经检查的转换。由于所有其他操作(例如 get(int))都使用类型参数 E,这将不安全类型处理限制在特定位置,实现者可以确保正确处理转换。

这是一些非通用代码:

public class MyList {

    List myData = new ArrayList();

    public void add(Object ob) {
        myData.add(ob);
    }

    public Object getAtIndex(int ix) {
        return myData.get(ix);
    }
}

此代码将允许您将任何类型的对象存储到您的 MyList 实例中即使MyList 的契约指定对象必须都是同一类型。此外,如果您使用 MyList 实例存储 String 实例,则必须在检索它们时手动将它们转换为 String。

String myString = (String) myList.get(1);

上面的 MyList class 不是类型安全的。如果将 String 实例以外的对象存储到您的 MyList 实例中,则上述赋值语句很可能会因 ClassCastException 而失败(这可能会在 运行 时发生,没有任何投诉)。

这是一个通用的 MyList class:

public class MyList<T> {

    List<T> myData = new ArrayList<>();

    public void add(T ob) {
        myData.add(ob);
    }

    public T getAtIndex(int ix) {
        return myData.get(ix);
    }
}

现在,编译器保证只能从 MyList 实例中添加和检索 T 个实例。因为编译器保证总是返回 T 个实例,所以您可以使用这样的语法而无需任何手动转换:

String myString = myList.get(1);

泛化的 MyList class 现在是类型安全的。编译器不允许您将 T 实例以外的任何东西存储到您的 MyList 实例中,这保证在 运行 时不会发生 ClassCastExceptions。如果您检查字节码,您会发现编译器已经自动进行了转换。

Java 中的泛型是一种仅编译时现象。在字节码中,上面 MyList class 中对 T 的所有引用都被替换为 Object。这个过程被称为"type erasure"。重要的是要记住 Java 中的类型安全仅由编译器提供。如果您的程序编译时没有任何错误 AND 且没有任何警告,那么您的程序、泛型和所有程序都是类型安全的。然而,编译后的程序几乎没有保留任何关于通用参数的信息。