引言
上节中简单介绍了泛型基本使用方法及使用泛型带来的安全性和可读性好处,接下来继续思考第二版代码的不足之处。回顾第二版Driver类的drive方法,其接收T泛型参数,为了调用具体car的run方法,需要向下转型,进行了多次instanceof判断,代码冗余且扩展性不好,不符合代码设计的"开闭原则"。有没有更好的方式?
根据“依赖反转”原则,即代码应当依赖于抽象,而不是具体。由于T类型可以是任意具体的类型,所以在方法体中只能当作一般的Object进行处理,那如果可以限制T的具体类型呢,让T类型必须是某个类的子类或实现某个接口呢,那么问题就解决了。请看改进后的代码
第三版
第三版代码如下:
public interface Runnable {
public void run();
}
public class Buick implements Runnable {
@Override
public void run(){
System.out.println("buick run");
}
public void autoRun(){
System.out.println("buick auto-run");
}
}
public class Ford implements Runnable {
@Override
public void run(){
System.out.println("ford run");
}
public void fly(){
System.out.println("ford fly");
}
}
public class Driver<T extends Runnable> {
private T car;
public void drive(T car){
this.car = car;
System.out.println("I am driving a " + car);
car.run();
}
public T getDrivingCar(){
return car;
}
public static void main(String[] args) {
Driver<Ford> driver1 = new Driver<Ford>();
driver1.drive(new Ford());
Driver<Buick> driver2 = new Driver<Buick>();
driver2.drive(new Buick());
//...执行其它业务逻辑
/** 获取司机1开的福特车执行fly方法 */
driver1.getDrivingCar().fly();
/** 获取司机2开的别克车执行autoRun方法 */
driver2.getDrivingCar().autoRun();
}
}
跟前一版本相比,增加了Runnable接口(不要跟多线程的Runnable接口混洧,如有雷同,纯属巧合),Ford和Buick都实现了该接口。最主要的修改在Driver类的泛型T声明上,将<T>改为<T extends Runnable>,这样改的意思是指,T泛型代表任意实现或继承Runnable接口的具体类型,这样就能够在方法体中直接调用Runnable接口中的run方法。
这样改的另一个好处是,由于drive方法接收的参数是基于接口的,这样即使传入参数是另外的车类型(比如奔驰车),代码也不需要进行任何修改,完全符合代码设计中“对扩展开放,对修改关闭”的原则。
泛型声明<T extends Runnable>意味着T类型必须是Runnable接口的子接口或实现类。更一般化的,可以是<T extends Parent>,Parent可以是接口,抽象类或一般类。T具体类型必须是Parent的子类或实现类或Parent自身。
上面介绍了extends关键字在泛型声明中的使用。下面介绍下如何在方法上进行泛型声明并使用。今天就写到这了,明天继续