考虑下面的示例代码
/* The new Java 5 or later Generic code */
class TestInserter {
public static void main(String[] ar) {
List<Integer> myList = new ArrayList<Integer>();
myList.add(20);
myList.add(42);
Inserter ins = new Inserter();
ins.insert(myList);
}
}
/* Legacy Code */
class Inserter {
public void insert(List list) {
list.add(new Integer(55));
}
}
编译和执行上面的代码会运行得很好,没有来自编译器或 JVM 的任何提示。 非类型安全
insert() 方法在我们的类型安全
ArrayList 中插入一个新的 Integer 对象。但是,如果我将插入方法更改为如下所示:
public void insert(List list) {
list.add(new String("55"));
}
说什么?!上面的代码会运行吗?想一想,是的,它肯定会运行,这很奇怪,但是是的,上面的代码编译并运行得很好。
这与 Arrays 有点不同,Arrays 为您提供编译时和运行时保护,并防止此类事情发生。他们为什么这样做泛型?为什么 Java 允许泛型输入指定类型以外的值?!
现在是答案。使用 insert
方法将 Integer
添加到我们的列表中是非常安全和允许的,因为它与我们为 myList
变量指定的类型相匹配.但是,当我们尝试将一个 String
插入到一个只包含 Integer
值的 ArrayList
中时,就会出现问题,不是在编译时,而是在运行时,当您尝试在错误添加的 String
实例上调用特定于 Integer
的方法时。
为了理解整个问题及其目的,您应该了解一件事 - JVM 不知道您试图将 String
插入 ArrayList
意味着仅包含Integer
。您所有的泛型及其类型安全性都仅限于编译时。通过称为“类型删除”的过程,编译器从通用代码中删除所有类型参数。换句话说,即使你写了这样的东西:
List<Integer> myList = new ArrayList<>();
编译完成后变成如下:
List myList = new ArrayList();
但是,为什么他们要这样保留泛型呢?
答案很简单!如果不是因为这种奇怪的行为,那么早期 Java 版本的遗留代码就会被破坏,数百万 Java 开发人员将不得不编辑数万亿的旧 Java 代码以使其再次运行!
但是不要责怪编译器;当您尝试运行代码时,编译器会尝试向您发出以下警告:
$> javac TestInserter.java
Note: TestInserter.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
当您按照编译器的要求执行以下操作时:
$> javac -Xlint:unchecked TestInserter.java
TestInserter.java:15: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
list.add(new String("55"));
^
where E is a type-variable:
E extends Object declared in interface List
1 warning
就编译器而言,它试图告诉您它怀疑您程序中的某些代码可能最终会遇到麻烦。
总而言之,将泛型视为编译时保护。编译器使用类型信息(参数中指定的类型)来确保您不会将错误的内容插入到集合(或用户定义的泛型类型)中,并且您不会从错误的引用类型中获取值。 所有通用保护都是编译时的!就是这样。