泛型是Java1.5以后的特性,在各种开源框架的源码当中可以看到很多泛型的使用,如果不对泛型做到了如指掌的话,看源码真的会有阻碍。下面是泛型的一个简单的例子。
public class GenericsDemo<T> {
private T demoProp;
public T getDemoProp(){
return demoProp;
}
public void setDemoProp(T demoProp){
this.demoProp = demoProp;
}
}
GenericsDemo类声明了一个泛型参数,T可以代表任何一个类型,在编译时,编译器并不知道对象在实例化过程中使用的T类型到底是什么,泛型是运用在编译时期的技术。
GenericsDemo的成员变量demoProp是T类型的,这个变量的类型也是在对象实例化的过程中才会指定的。
public static void main(String args[]){
GenericsDemo<String> demoStr = new GenericsDemo<String>();
GenericsDemo<Integer> demoInt = new GenericsDemo<Integer>();
GenericsDemo<Double> demoDoub = new GenericsDemo<Double>();
String str = demoStr.getDemoProp();
Integer inte = demoInt.getDemoProp();
Double doub = demoDoub.getDemoProp();
}
在使用过程中,分别指定String, Integer, Double为参数对GenericsDemo进行实例化,然后分别用这三个类型去接收demoProp的值,不会有error或者warning。
但是如果使用比如int会报错,因为泛型参数只能替换为对象类型,而int类型是基础数据类型,不是对象。
GenericsDemo<int> demoInt = new GenericsDemo<int>();//编译报错
接收的变量类型和实例化对象时使用的泛型参数类型不匹配时也会报错
String inte = demoInt.getDemoProp();//编译报错
类声明时可以指定不止一个泛型参数
public class GenericsDemo<T,E> {
private T demoProp;
public T getDemoProp(){
return demoProp;
}
public void setDemoProp(T demoProp){
this.demoProp = demoProp;
}
public void process(E element){
System.out.println("processing");
}
}
如果我们预设实例化时,T,E传入的是某一类对象,并且想使用对象的方法和属性,我们就必须对T,E进行限定。不做限定的T,E只能使用Object的方法,因为所有对象都派生自Object。
public class GenericsDemo<E> {
public void process(E element){
//可以使用Object的getClass()方法
System.out.println(element.getClass().getName());
}
}
可以使用extends和super对泛型参数添加限定,extends的使用举例,定义People类,实现了Action接口。
public class People implements Action{
public void walk() {
System.out.println("walking");
}
}
public interface Action {
public void walk();
}
public class Male extends People{
public void shave(){
System.out.println("only man can shave");
}
}
重新定义GenericsDemo
public class GenericsDemo<T extends People> {
private T demoProp;
public T getDemoProp(){
return demoProp;
}
public void setDemoProp(T demoProp){
this.demoProp = demoProp;
}
public void move(){
demoProp.walk();
}
}
<T extends People>代表泛型参数T是People的子类型,对T进行限定了以后,编译器知道T肯定是People的子类型,就可以使用People的方法了。上例中通过demoProp.walk()直接调用。
实例化GenericsDemo时,可以传入People或者其子类
public static void main(String args[]){
GenericsDemo<People> demoPeople = new GenericsDemo<People>();
GenericsDemo<Male> demoMale = new GenericsDemo<Male>();
demoPeople.setDemoProp(new People());
demoPeople.move();
demoMale.setDemoProp(new Male());
demoMale.move();
}
没有报错,运行正常,说明extends边界包含T本身。
但是要注意的是,extends和我们继承时候使用的extends不是相同的,这里的extends是子类型的意思,不是子类,对于接口也是可行的,比如这么写
public class GenericsDemo<T extends Action> {
private T demoProp;
public T getDemoProp(){
return demoProp;
}
public void setDemoProp(T demoProp){
this.demoProp = demoProp;
}
public void move(){
demoProp.walk();
}
}
依然能够正常编译和运行,是因为People和Male都实现了Action接口,也是Action派生出来的类。
super和extends相反,通过super限定的泛型参数,比如<? super Male>,代表泛型参数是指定类型的父类型。
super只能用在泛型参数,不能用在类声明中,很好理解,对于extends的情况,编译器知道继承的具体父类型是谁,所以自然对象可以使用哪些方法是具体的。对于super的情况,编译器并不知道父类具体是谁,所以父类型有哪些方法编译器是不知道的,所以在类的声明中,<? super Male> 和<T>在编译器看来没什么区别。
//编译报错
public class GenericsDemo<T super Male> {
}
通配符?
在泛型类的使用中,有时候不知道最后传入的参数是什么类型的对象,会使用通配符?来代替,比如
public static void main(String args[]){
GenericsDemo<?> demoPeople;
demoPeople = new GenericsDemo<People>();
demoPeople = new GenericsDemo<Male>();
demoPeople.move();
}
比如我们使用?来声明demoPeople,没有给他指定特定的对象类型,所以他的对象类型是可以任意的,所以可以使用People和Male来初始化他,都不会报错。
?不能出现在等号右边,必须告诉编译器你打算用什么类型初始化。
?通配符声明的对象在初始化以后不能对泛型成员变量进行set。
//报错
demoPeople = new GenericsDemo<?>();
//报错
demoPeople.setDemoProp(new People());
通配符和限定词配合使用
public class GenericsESDemo {
public void testSuper(GenericsDemo<? super Male> generic){
System.out.println("testing super");
}
public void testExtends(GenericsDemo<? extends People> generic){
System.out.println("testing extends");
}
public static void main(String args[]){
GenericsESDemo demo = new GenericsESDemo();
demo.testSuper(new GenericsDemo<People>());
demo.testExtends(new GenericsDemo<Male>());
}
}
可以限制传入的泛型参数是某些特定类型的父类型或者子类型,使用可以更加灵活。