泛型是在JDK5引入的特性。它允许我们定义的类和接口的时候使用类型参数。它被广泛运用在java集合框架中。类型擦出概念是关于泛型是最容易困惑的部分之一。本文将教你如何使用她。

1.一个常见错误

在下面的例子,accept 方法接受了一个Object集合来作为参数,在main方法中调用却是使用String的集合。它能正常调用吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main(String[] args) throws IOException {
ArrayList<String> al = new ArrayList<String>();
al.add("a");
al.add("b");
accept(al);
}
public static void accept(ArrayList<Object> al){
for(Object o: al)
System.out.println(o);
}
}

这看起来是否没什么问题,因为Object明显是String的超类。但是,这代码是不可能正常的,编译都不会通过的,在accept(al);这行是会有错误的。

accept(ArrayList < Object > )方法和Main中调用是不一致的(ArrayList < String > )

2. List < Object > vs. List < String >

这原因是因为类型擦出。注意:java泛型是编译级别的。在运行时由编译器生成的字节码是不包含泛型的类型信息的。

编译后,不管是Object的List还是String的List都会变成List,同时Object/String类型在JVM中都是不可访问的。在编译阶段,编译器发现它们不相同,使用报了一个编译错误。

3. 通配符和有界通配符

List< ? >- List可以包含任何类型

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String args[]) {
ArrayList<Object> al = new ArrayList<Object>();
al.add("abc");
test(al);
}
public static void test(ArrayList<?> al){
for(Object e: al){//不管是什么类型,都会是 Object
System.out.println(e);
// 在该方法中,因为我们不知道什么类型?所以,我们可不加任何类型到al。
}
}

必须记住,泛型是在编译时期的。在上面的例子中,不是我们没注意,我们不能给al添加任何类型。为了让其能添加,你可以使用通配符。

List< Object > - List 能包括 Object 或者 其子类

List< ? extends Number > -  List 能包括Number及其子类
List< ? super Number > - List 能包括 Number 及其父类

4.比较

现在我们知道ArrayList < String > 并不是ArrayList < Object >的子类。作为对比,你应该知道,如果两个泛型类型具有相同的参数,它们的继承关系是真正的。比如ArrayList < String >Collection< String>的子类。

数组是不同的。他们在运行时知道执行它们的元素类型。这就是所谓的具体化。比如Object[] objArrayString[] strArr的父类。如果您尝试将字符串保存到整数的数组,你会在运行时得到异常ArrayStoreException。

延伸阅读

参考文档