泛型是Java 5开始引入的一个语言级别的特性,目的是为了让你的程序更为可靠(reliable)。
程序总是有bug,而bug分为编译时bug和运行时bug,编译时bug是比较令人舒服的bug,因为大部分的IDE比如eclipse,就能让你在写代码的时候就能发现并及时fix。但是运行时bug就很难受了,必须在运行时的某个条件下才能出现,当你想去找它的时候还不一定那么好找,尤其是多线程环境下的bug,更难重现。我们如果能尽量使bug可以在编译时发现,那就再好不过了。
泛型的引入就是为了让你尽量在编译时就发现某些类型的bug。典型的例子就是Collection类,如果你在用Collection的时候放入的对象的类型不匹配,编译器就能告诉你,要不然,得在运行时才能发现。
这个问题的核心在于强制类型转换。既然是强制类型转换,就存在转换失败的可能,比如你想将String强转成Integer,就是出强制类型转换失败的错误。作为容器类,或者说集合类,如果对放入容器的对象编译器不做类型检查,那就只能到运行的时候检查了,这显然不利于发现bug。通过Generic,编译器就可以知道容器里应该放入对象的类型,那么,编译器就能帮我们发现这类bug了。所以,当你用强制类型转换的时候,你就得考虑是否需要设计Generic来保护你的程序不会发生ClassCastException。
泛型本质上是将类型作为参数,类,接口和方法的定义中都可以带有这种参数。也可以这样想,你可以在定义类,接口和方法的时候,加上某个或某几个参数,而这个参数必须得是某个类或者接口。这样定义了类或者接口以后,当别的类使用这些类或接口的时候就得带上具体的类型(当然也可以不带,但是不推荐这样)。
下面看一个例子:
这个例子定义了一个容器类Box,里面可以放一个任意类型的对象,但是你得在new这个容器的时候声明你到底要放什么类型的对象在里面。
BoxClient类里面定义了两个fillBox方法,其中一个就是参数化的方法定义。没有这个定义能力你就得像那个传入Integer方法一样,一一列举你要传入的类型。
有意思的是,在29行,调用的是哪一个呢?因为两个fillBox方法都能符合?看看输出你就知道了。你也可以试一试把第一个fillBox方法删除,在运行一下看看。
实际上,你大可以删除第一个fillBox方法。
输出:
泛型只是为了让编译器帮我们找到类型上的错误,在编译以后生成的class文件里面并没有泛型的概念,也就是说,下面的使用是不合法的:
[size=medium]
使用陷阱(pitfall)[/size][/b]
1. 泛型不是协变的(covariant)
Java里面的数组是协变的。我们知道Integer是Number的子类,那么Integer[]是不是Number[]的子类呢?答案是肯定的。这就是协变的意义所在。
那么List<Integer> 是不是 List<Number>的子类呢?不是的。也就是泛型没有协变性。
这样,你就不能把List<Integer>变量赋值给List<Number>变量。看下面的例子:
如果ln=li可以的话,那么它的下一句类型的转化就有问题了,相当于将Float强转给Integer了。这样也就破坏了[b]Generic用来保证类型安全[/b]的意义了。
[url]
http://www.ibm.com/developerworks/java/library/j-jtp01255/index.html[/url]
[url]
http://www.ibm.com/developerworks/java/tutorials/j-generics/section6.html[/url]
程序总是有bug,而bug分为编译时bug和运行时bug,编译时bug是比较令人舒服的bug,因为大部分的IDE比如eclipse,就能让你在写代码的时候就能发现并及时fix。但是运行时bug就很难受了,必须在运行时的某个条件下才能出现,当你想去找它的时候还不一定那么好找,尤其是多线程环境下的bug,更难重现。我们如果能尽量使bug可以在编译时发现,那就再好不过了。
泛型的引入就是为了让你尽量在编译时就发现某些类型的bug。典型的例子就是Collection类,如果你在用Collection的时候放入的对象的类型不匹配,编译器就能告诉你,要不然,得在运行时才能发现。
这个问题的核心在于强制类型转换。既然是强制类型转换,就存在转换失败的可能,比如你想将String强转成Integer,就是出强制类型转换失败的错误。作为容器类,或者说集合类,如果对放入容器的对象编译器不做类型检查,那就只能到运行的时候检查了,这显然不利于发现bug。通过Generic,编译器就可以知道容器里应该放入对象的类型,那么,编译器就能帮我们发现这类bug了。所以,当你用强制类型转换的时候,你就得考虑是否需要设计Generic来保护你的程序不会发生ClassCastException。
泛型本质上是将类型作为参数,类,接口和方法的定义中都可以带有这种参数。也可以这样想,你可以在定义类,接口和方法的时候,加上某个或某几个参数,而这个参数必须得是某个类或者接口。这样定义了类或者接口以后,当别的类使用这些类或接口的时候就得带上具体的类型(当然也可以不带,但是不推荐这样)。
下面看一个例子:
package jdk.generic;
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
class BoxClient {
public static void fillBox(Integer o, Box<Integer> box) {
System.out.println("fillBox Integer");
box.add(o);
}
public static <U> void fillBox(U u, Box<U> box) {
System.out.println("fillBox U");
box.add(u);
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
fillBox(1, integerBox);
Box<String> stringBox = new Box<String>();
fillBox("abc", stringBox);
BoxClient.<Integer>fillBox(1, integerBox);
}
}
这个例子定义了一个容器类Box,里面可以放一个任意类型的对象,但是你得在new这个容器的时候声明你到底要放什么类型的对象在里面。
BoxClient类里面定义了两个fillBox方法,其中一个就是参数化的方法定义。没有这个定义能力你就得像那个传入Integer方法一样,一一列举你要传入的类型。
有意思的是,在29行,调用的是哪一个呢?因为两个fillBox方法都能符合?看看输出你就知道了。你也可以试一试把第一个fillBox方法删除,在运行一下看看。
实际上,你大可以删除第一个fillBox方法。
输出:
fillBox Integer
fillBox U
fillBox U
泛型只是为了让编译器帮我们找到类型上的错误,在编译以后生成的class文件里面并没有泛型的概念,也就是说,下面的使用是不合法的:
public void myMethod(Object item) {
// Compiler error
if (item instanceof T) {
}
T item2 = new T(); // Compiler error
T[] iArray = new T[10]; // Compiler error
}
[b]
[size=medium]
使用陷阱(pitfall)[/size][/b]
1. 泛型不是协变的(covariant)
Java里面的数组是协变的。我们知道Integer是Number的子类,那么Integer[]是不是Number[]的子类呢?答案是肯定的。这就是协变的意义所在。
那么List<Integer> 是不是 List<Number>的子类呢?不是的。也就是泛型没有协变性。
这样,你就不能把List<Integer>变量赋值给List<Number>变量。看下面的例子:
public class Covariant {
public static void main(String[] args) {
Integer[] i = new Integer[1];
Number[] n = i; //legal
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li;//illegal
ln.add(new Float(3.1415)); //legal
li.add(new Float(3.1415)); //illegal
}
}
如果ln=li可以的话,那么它的下一句类型的转化就有问题了,相当于将Float强转给Integer了。这样也就破坏了[b]Generic用来保证类型安全[/b]的意义了。
[url]
http://www.ibm.com/developerworks/java/library/j-jtp01255/index.html[/url]
[url]
http://www.ibm.com/developerworks/java/tutorials/j-generics/section6.html[/url]