java泛型学习和实践(4)

引言

前三节讲述了泛型常见声明及使用,泛型既可以在类上进行声明,也可以在单个方法上进行声明,并分别对这两种情况进行了总结。下面来学习下泛型扩展知识。

延用前面的Runnable接口、Buick类、Ford类、Driver类,新增加一个汽车容器类CarContainer

第一版

代码如下:

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");
    }
}
<pre name="code" class="java">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 class CarContainer<E extends Runnable> {
    private List<E> container = new ArrayList<E>();

    public void add(E e) {
        container.add(e);
    }

    public void add(Driver<E> producer) {
        container.add(producer.getDrivingCar());
    }

    public static void main(String[] args) {
        CarContainer<Runnable> container = new CarContainer<Runnable>();
        Buick buick = new Buick();
        container.add(buick);

        Driver<Runnable> driver = new Driver<Runnable>();
        driver.drive(buick);
        container.add(driver);
    }
}

前三个类不多说了,不明白的请参照前三节。说下CarContainer类, add方法接收泛型E类型对象,向容器中新增汽车。另一个重载add方法接收Driver<E>参数,获取到drivingCar并增加到容器中。

main静态方法中分别演示了这两个add的使用方法。先看第一个add方法使用,由于Buick类实现了Runnable接口的,container.add(buick)这样调用是没问题的;再看第二个add方法使用:首先driver.drive(buick)也没问题,原理同上;然后看container.add(driver),由于container的元素类型为Runnable接口,而driver的元素类型也为Runnable接口,类型是完全匹配的,因此运行下程序也没问题,看上去都很美好。

假如有一个Driver<Buick>呢,请大家思考下这样调用看行不行

        Driver<Buick> driver = new Driver<Buick>();
        driver.drive(buick);
        container.add(driver);

结果是不行,编译时提示如下错误:

The method add(Runnable) in the type CarContainer<Runnable> is not applicable for the arguments (Driver<Buick>)

从逻辑上说,这样调用应该是可以的,因为Buick实现了Runnable接口,但实际上不行。还好,有一种解决方法。JAVA提供了一种特殊化的参数类型,称作有限制的通配符类型(bounded wildcard type),来处理类似的情况。我们的想法是第二个add方法接收的参数应该是“E的某个子类型的Driver”,可通过Driver<? extends E>实现。?代表任意未知类型,附加extends E限制,表示该未知类型必须是E的子类或其自身。修改后的代码如下:

第二版

    public void add(Driver<? extends E> producer) {
        container.add(producer.getDrivingCar());
    }

这样修改后,演示程序可正常能过编译,也能正常运行,说明这样是类型安全的。

上面第一次出现跟泛型相关的通配符?,表示未知类型,下面简单说下其用法。

?

? 一般出现在方法参数上,代表未知类型。注意与泛型声明中的E区别。泛型E相当于一个类型占位符,可以在类中多处出现并意味着这几处将来的具体类型是一样的(例如Driver类和CarContainer中的泛型);而?代表某个未知的具体类型,类多个地方出现的?没有任何关联。

?支持<? extends Parent>用法,代表未知类型必须是Parent的子类或自身,与泛型中的<E extends Parent>意义很相似。

?还支持<? super Child>用法,代表未知类型必须是Child的超类或自身,这种用法在泛型中不存在(因为无意义),请注意。

上面总结中提到<? super Child>用法,接下来补充学习下super的使用场景。

对于CarContainer类,跟add方法对应的,我们新增一个pop方法,如下:

    public void pop(Set<E> consumer){
        consumer.add(container.remove(container.size()));
    }

接收集合类consumer参数,方法体中从container中移除最后一个元素并加入到consumer中。使用示例如下:

        CarContainer<Runnable> container = new CarContainer<Runnable>();
        Buick buick = new Buick();
        container.add(buick);

        Set<Runnable> consumer = new HashSet<Runnable>();
        container.pop(consumer);

假如你有另一个Set<Object> consumer = new HashSet<Object>()呢,如果按照上面调用,会出现编译错误:

The method pop(Set<Runnable>) in the type CarContainer<Runnable> is not applicable for the arguments (Set<Object>)

我们的想法是,pop接收的参数应用是"E的某种超类的Set",通过?和super关键字配合使用正好可以达到这种目的,即Set<? super E>,修改后代码如下:

第三版

    public void pop(Set<? super E> consumer){
        consumer.add(container.remove(container.size()));
    }

修改后,支持了Set<Object> consumer调用方式。

通过?跟extends或super关键字在方法参数上的搭配使用,可以获得API最大限度的灵活性。

那么什么情况下该使用extends关键字,什么情况下该使用super关键字。有一个原则叫PECS,即producer-extends,consumer-super。

PECS意思是,如果带泛型参数化类型表示一个E生产者,就使用<? extends E>;如果它表示一个E消费者,就使用<? super E>。在上述示例中,add的producer参数产生E的实例供CarContainer使用,因此producer相应的类型为Driver<? extends E>;pop的consumer参数通过CarContainer消费E实例,因此consumer参数相应的类型为Set<? super E>。如果参数即是生产者又是消费者呢,那就不适合使用通配符?,因为你需要的是严格的类型匹配,即直接使用Set<E>

上面关于泛型的通配符?、extends和super的使用方法参照了《Effective.Java_2中文版.pdf》一书,由于水平有限,可能大家还没看明白。欢迎大家提问。

关于这讲实践演示,暂时在项目中没有用到,以后补充

结束语

关于泛型的学习和实践到此结束,总共四节,谢谢大家的关注。等有时间了,想再跟大家一起学习下设计模式,我觉得这也是每个Java程序员的必修课。敬请期待

时间: 2024-10-25 12:05:42

java泛型学习和实践(4)的相关文章

java泛型学习和实践(1)

引言 JDK1.5之后引入了泛型,泛型刚开始接触时比较难理解,经过慢慢学习,有一些收获,现总结下,既可以加深自身理解,也可以帮助他人. 由于本人水平有限,难免有不对的地方,还请指正.废话不多说,下面正式开始. 为了更直观的说明泛型,现假设一个司机开车的场景.我们需要3个类,Driver(司机).Ford(福特).Buick(别克). 第一版 第一版本代码如下: public class Buick { public void run(){ System.out.println("buick ru

java 泛型学习

http://blog.csdn.net/archie2010/article/details/6232228 学习集合框架的时候经常用hasmap<Integer,Integer>就是泛型,c++里面叫模板,其实我是想研究一下迭代器模式的.睡觉,明天再说. 1 import java.util.ArrayList; 2 import java.util.Collection; 3 4 class A< T extends Collection> 5 { 6 private T x

java泛型学习(2)

一:深入泛型使用.主要是父类和子类存在泛型的demo /** * 父类为泛型类 * @author 尚晓飞 * @date 2014-7-15 下午7:31:25 * * * 父类和子类的泛型. * [泛型的具体声明] * (1)子类直接声明具体类型 * (2)使用时指定具体类型(new 对象时) * (3)子类泛型>=父类泛型(个数,类型,顺序无关) * * [泛型的确定] * (1)属性:在父类中,泛型随父类泛型而定 * 子类中,泛型随子类泛型而定 * (2)重写方法中: * 泛型全部随父类

Java泛型学习笔记--Java泛型和C#泛型比较学习(一)

总结Java的泛型前,先简单的介绍下C#的泛型,通过对比,比较学习Java泛型的目的和设计意图.C#泛型是C#语言2.0和通用语言运行时(CLR)同时支持的一个特性(这一点是导致C#泛型和Java泛型区别的最大原因,后面会介绍).C#泛型在.NET CLR支持为.NET框架引入参数化变量支持.C#泛型更类似C++模板,可以理解,C#泛型实际上可以理解为类的模板类.我们通过代码实例来看C# 2.0泛型解决的问题,首先,我们通过一个没有泛型的迭代器的代码示例说起,代码实现如下: interface

Java泛型学习笔记 - (七)浅析泛型中通配符的使用

一.基本概念:在学习Java泛型的过程中, 通配符是较难理解的一部分. 主要有以下三类:1. 无边界的通配符(Unbounded Wildcards), 就是<?>, 比如List<?>. 无边界的通配符的主要作用就是让泛型能够接受未知类型的数据. 2. 固定上边界的通配符(Upper Bounded Wildcards): 使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据. 要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是

Java泛型学习

先引用一段Java编程思想中的一段话 Java泛型的核心概念:告诉编译器你想使用什么类型,然后编译器帮你处理一切细节. 关于类型推断: 类型推断只对赋值操作有效,其他时候并不起作用.如果你见过一个泛型方法调用的结果 作为参数传递给另一个方法,这时编译器不会执行类型推断.在这种情况下,编译器认为:调用泛型 方法后,其返回值被付给了一个Object变量.当然现在在Java8先已经可以了

Java泛型学习笔记 - (六)泛型的继承

在学习继承的时候, 我们已经知道可以将一个子类的对象赋值给其父类的对象, 也就是父类引用指向子类对象, 如: 1 Object obj = new Integer(10); 这其实就是面向对象编程中的is-a关系. 既然上面的代码正确, 那么在泛型中, 也可以使用如下代码: 1 public class Box<T> { 2 private T obj; 3 4 public Box() {} 5 6 public T getObj() { 7 return obj; 8 } 9 10 pub

Java泛型学习笔记 - (二)泛型类

1. 我们先写一个没有泛型的类Box: 1 public class Box { 2 3 private Object obj; 4 5 public Box() {} 6 7 public Object getObj() { 8 return obj; 9 } 10 11 public void setObj(Object obj) { 12 this.obj = obj; 13 } 14 15 public Box(Object obj) { 16 super(); 17 this.obj

Java泛型学习笔记 - (三)泛型方法

泛型方法其实和泛型类差不多, 就是把泛型定义在方法上, 格式大概就是: public <类型参数> 返回类型 方法名(泛型类型 变量名) {...}泛型方法又分为动态方法和静态方法,:1. 动态泛型方法其实在前一篇博文中我已经用到了, 1 public class Box<T> { 2 3 private T obj; 4 5 public Box() {} 6 7 public T getObj() { 8 return obj; 9 } 10 11 public void se