`
tang9140
  • 浏览: 33360 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

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程序员的必修课。敬请期待

版权声明:本文为博主原创文章,未经博主允许不得转载。

分享到:
评论

相关推荐

    JDK 5.0中的泛型类型学习

    在本月的“Java 理论和实践”中,Brian Goetz 分析了束缚第一次使用泛型的用户的常见陷阱。您可以通过讨论论坛与作者和其他读者分享您对本文的看法。(也可以单击本文顶端或底端的讨论来访问这个论坛。)

    【Java面试+Java学习指南】 一份涵盖大部分Java程序员所需要掌握的核心知识

    Java注解和最佳实践 JavaIO流 多线程 深入理解内部类 javac和javap Java8新特性终极指南 序列化和反序列化 继承、封装、多态的实现原理 容器 Java集合类总结 Java集合详解1:一文读懂ArrayList,Vector与Stack使用...

    Java语音学习,基础教程

    学习集合:理解Java中的Collection、泛型、List、Set、Collections、Map和HashMap等概念和用法。 掌握异常处理:了解Java的异常体系、异常分类,学会声明、抛出和捕获异常,并自定义异常。 初步掌握多线程:理解线程...

    2023Java的学习

    Java的学习过程需要不断地实践和探索,通过编写实际的程序来理解和掌握Java语言。同时,还需要不断地学习和掌握Java语言的新特性和新技术,以适应不断变化的编程环境和需求。 总之,Java是一种强大而灵活的编程语言...

    Java工程师面试复习指南

    Java注解和最佳实践 JavaIO流 多线程 深入理解内部类 javac和javap Java8新特性终极指南 序列化和反序列化 继承封装多态的实现原理 集合类 Java集合类总结 Java集合详解:一文读懂ArrayList,Vector与Stack使用方法和...

    Java.In.A.Nutshell 7th Covers Java11.pdf

    Chock充满了演示如何充分利用现代Java API和开发最佳实践的示例,这本经过全面修订的书籍包含有关Java Concurrency Utilities的新资料。 本书的第一部分提供了Java编程语言和Java平台的核心运行时方面的快节奏,...

    Java JDK 7学习笔记(国内第一本Java 7,前期版本累计销量5万册)

     《java jdk 7学习笔记》是作者多年来教学实践经验的总结,汇集了教学过程中学生在学习java时遇到的概念、操作、应用或认证考试等问题及解决方案。  《java jdk 7学习笔记》针对java se 7新功能全面改版,无论是...

    核心技术与最佳实践

    本书是学习Java 7 新功能和新特性以及深入理解Java 核心技术的最佳选择之一。经过 近6 年的等待,Java 迎来了它的又一个历史性的版本—Java 7 。Java 7 在提高开发人员的 生产效率、平台性能和模块方向上又迈进了...

    Java SE实践教程 源代码 下载

    本书从编程技术、项目实践以及软件工程的角度出发,如果大家想学习基础语法部分,建立去下载别的书籍。当然这本书也讲解了语法,是从实战的角度讲解的。 目录回到顶部↑第1章 进驻爪哇岛——JAVA的基本语法. 1 1.1 ...

    Java SE实践教程 pdf格式电子书 下载(四) 更新

    本书从编程技术、项目实践以及软件工程的角度出发,如果大家想学习基础语法部分,建立去下载别的书籍。当然这本书也讲解了语法,是从实战的角度讲解的。 目录回到顶部↑第1章 进驻爪哇岛——JAVA的基本语法. 1 1.1 ...

    基于java的电子相册源码.zip

    通过阅读和分析Java源码,可以帮助学习者深入理解Java编程语言的特性和最佳实践,提高编程技能,解决实际问题。同时,Java源码也是开发人员进行软件开发的基础,可以用于构建各种类型的应用程序和系统。 这个Java...

    基于java的日记本源码.zip

    通过阅读和分析Java源码,可以帮助学习者深入理解Java编程语言的特性和最佳实践,提高编程技能,解决实际问题。同时,Java源码也是开发人员进行软件开发的基础,可以用于构建各种类型的应用程序和系统。 这个Java...

    基于java的考勤管理系统源码.zip

    通过阅读和分析Java源码,可以帮助学习者深入理解Java编程语言的特性和最佳实践,提高编程技能,解决实际问题。同时,Java源码也是开发人员进行软件开发的基础,可以用于构建各种类型的应用程序和系统。 这个Java...

    基于java的SSM商城系统源码.zip

    通过阅读和分析Java源码,可以帮助学习者深入理解Java编程语言的特性和最佳实践,提高编程技能,解决实际问题。同时,Java源码也是开发人员进行软件开发的基础,可以用于构建各种类型的应用程序和系统。 这个Java...

    基于java的物流信息网源码.zip

    通过阅读和分析Java源码,可以帮助学习者深入理解Java编程语言的特性和最佳实践,提高编程技能,解决实际问题。同时,Java源码也是开发人员进行软件开发的基础,可以用于构建各种类型的应用程序和系统。 这个Java...

    基于java的超市管理系统源码.zip

    通过阅读和分析Java源码,可以帮助学习者深入理解Java编程语言的特性和最佳实践,提高编程技能,解决实际问题。同时,Java源码也是开发人员进行软件开发的基础,可以用于构建各种类型的应用程序和系统。 这个Java...

Global site tag (gtag.js) - Google Analytics