参考链接: Java中的协变返回类型

在《JAVA核心思想》这本书里,关于泛型的章节意外的很多,小小的泛型里其实有很多可以学习的内容,我总结下最近看书的成果。 

一. 泛型的好处和应用 

最基础的用到泛型的地方无非是在容器里 使用泛型用以保证容器内数据的类型统一,所以我们先总结下泛型使用的好处: 

可以统一集合类型容器的存储类型,防止在运行期出现类型装换异常,增加编译时类型的检查解决重复代码的编写,能够复用算法。可以起到重载的作用 

第二个作用很好理解,泛型 的 ‘泛’ 即代表着 ‘泛化’,不仅仅是保证容器的安全性,更重要的是减少类型的限制,让我们编写更加通用的代码。我们举个例子:  在javaweb中,我们经常向前台返回JSON对象信息,不同的业务可以会组装不同的bean,为了节省提高代码的复用性,我们可以这么写一个类: 

public class ReturnObject<A, B> {

 

    public final A a;

    public final B b;

 

    public ReturnObject(A a, B b) {

        this.a = a;

        this.b = b;

    }

 

    public <A> A t(A a) {

        return a;

    }

 

    @Override

    public String toString() {

        return "ReturnObject{" +

                "a=" + a +

                ", b=" + b +

                '}';

    }

这个类没有具体的类型,意思也很简单,就是不限定变量的类型,根据你业务的不同,你可以传不同的类型进去(通过构造器),更方便的是,java泛型支持继承,你可以随意拓展你的业务字段,也就不再需要为了一种业务专门创建一个bean类了。 

// 业务字段拓展

public class ReturnObjectExtender<A, B, C> extends ReturnObject<A, B> {

 

    public final C c;

 

    public ReturnObjectExtender(A a, B b, C c) {

        super(a, b);

        this.c = c;

    }

 

    @Override

    public String toString() {

        return "ReturnObjectExtender{" +

                "c=" + c +

                ", a=" + a +

                ", b=" + b +

                '}';

    }

二. 重要!泛型的擦除 

JAVA的泛型都是通过擦除来实现的,这句话的意思是 当你的程序真正跑起来的时候,任何具体的类型其实都已经被擦除了,所以在下面的例子中,输出的结果是true,aClass和aClass1都是一样的class生成的对象。 

Class aClass = new ArrayList<Integer>().getClass();

Class aClass1 = new ArrayList<String>().getClass();

 

System.out.println(aClass == aClass1);

 

擦除的负面效应直接体现在如果你写下面这段代码,T类型并不能认出你传给它的是String类型,T直接会被Object替代, 

public class WildCardTest<T> {

 

    public void f(T t) {

    //这一段编译报错

        t.isEmpty();

    }

 

    public static void main(String[] args) {

        WildCardTest<String> stringWildCardTest = new WildCardTest<>();

        stringWildCardTest.f("");

    }

要让String调用它的isEmpty()方法,需要给泛型一个边界,代码只需要重新改一下,T extends String表明T可以是String类或是String的子类,如果传入的没有问题,那就可以调用isEmpty()方法。 

public class WildCardTest<T extends String> {

 

    public void f(T t) {

        t.isEmpty();

    }

 

    public static void main(String[] args) {

        WildCardTest<String> stringWildCardTest = new WildCardTest<>();

        stringWildCardTest.f("");

    }

擦除是历史遗留问题 

java的泛型不是从jdk1.0就出现的,为了跟以往没有泛型代码的源代码兼容,例如List被擦除为List,而普通的类型变量在未指定边界的时候被擦除为Object,从而实现泛型的功能并且向后兼容。 

三. 泛型的通配符(逆变与协变) 

这是两个赋值,一个是数组,ArrayList因为是Collection的子类,所以数组向上转型是可以的;另一个是带泛型的ArrayList,带ArrayList的泛型并不能赋值给带Collection的泛型。 

Collection[] collections = new ArrayList[]{};

//泛型会报错

ArrayList<Collection> collections1 = new ArrayList<ArrayList>(); 

逆变,协变与不变 

为什么会导致这样的差异呢?这里又引出了一个概念– 逆变,协变与不变。逆变与协变用来描述类型转换后的继承关系。 

f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;

f(⋅)是协变(covariant)的,当A≤B时有成立f(A)≤f(B)成立;

f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。 

根据上面两个赋值语句做解释,A 是ArrayList ,B是Collection,所以B > A,这个没有问题,然后 A[] 数组当作f(A),B[] 数组当作f(B),并且A[]可以赋值给B[]数组,说明 f(B) >=f(A),符合协变原则,所以数组是协变的。  而泛型也套用这个规则,发现 泛型其实是不变的。 

Java中泛型是不变的,可有时需要实现逆变与协变,怎么办呢?这时,通配符?派上了用场: 

// <? extends>实现了泛型的协变,它意味着某种Collection或Collection子类的类型,规定了该容器持有类型的上限,它是具体的类型,只是这个类型是什么没有人关心  比如:

List<? extends Collection> list = new ArrayList<ArrayList>();

 

// <? super>实现了泛型的逆变,它意味着某种ArrayList或ArrayList父类的类型,规定了该容器持有类型的下限,比如:

List<? super ArrayList> list = new ArrayList<Collection>();  

逆变,协变的应用 

在协变中,还是先以数组做举例,协变中对持有对象的存入有严格限制: 

Collection[] collections = new ArrayList[2];

 

collections[0] = new ArrayList();

//这一步编译没问题。但是运行时发现数组里已经定好了只能存ArrayList类型,所以会抛ArrayStoreException

collections[1] = new LinkedList(); 

所以在泛型的协变中,例如ArrayList的add方法是不能调用了,在编译期间直接报错。 

这是ArrayList 的add,get方法定义,当使用协变时,E e 会被直接替换成 ? extends E 

public boolean add(E e);

public E get(int index); 

具体事例: 

ArrayList<? extends Set> sets = new HashSet<>();

 

//因为 ? extends Set 编译器不知道sets引用指向什么对象,有可能是 HashSet,可能是TreeSet,这种不确定性导致sets不能使用add方法。

//sets.add(new HashSet());

 

//能插入null值

sets.add(null);

 

//因为这个泛型参数的上限是Set,为了安全性,所以只返回set类型

Set set = sets.get(0); 

在逆变中,因为规定了泛型的下界,所以get set 方法的使用限制又有所不同: 

ArrayList<? super List> list = new ArrayList<Collection>();

 

//对于 add方法,只能放List或List的子类,因为list容器泛型参数都是List的父类 不会出现问题

list.add(new ArrayList());

//不能add HashSet

//list.add(new HashSet());

//不能add Object

//list.add(new Object());

 

//因为这个泛型参数的下限是List    所以无法确定这是个什么类型,只能返回Object

Object object = list.get(0); 

无界通配符 

除了extends和super,还有一种 List<\?>这种通配符,代表着任何事物,但它与List不同的是: 

List<\Object> = List = ‘持有任何Object类型的原生List’List<\?>表示–’具有某种特定类型的非原生List,只是我们不知道那种类型是什么’。 

具体用代码看出区别: 

ArrayList<Collection> collections2 = new ArrayList<>();

 

ArrayList<?> objects = new ArrayList<>();

//?代表持有某种特定类型 ,所以也可以是Collection,这种赋值时合法的

objects = collections2;

 

//?代表持有某种特定类型,d但是不确定具体哪种,所以只能返回Object

Object o = objects.get(0);

 

//?代表持有某种特定类型,但是什么类型编译器并不知道,所以为了安全起见,不会让你用add方法

//objects.add(new Object());

 

 

//可以add null

objects.add(null);

 

总结来说: 

要从泛型类取数据时,用extends;要往泛型类写数据时,用super;既要取又要写,就不用通配符(即extends与super都不用)。 

四. 总结 

这篇文章总结的是书里比较重要的知识点,跳过了简单的应用,写了那么多,感觉总结还是很有必要的,你第一遍看书也许概念会有些懵懂,但是再记录总结下,你会解开第一遍看书时有点么棱两可的知识点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/540312.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/540312.shtml
英文地址,请注明出处:http://en.pswp.cn/news/540312.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Session 详解

[ASP.NET] Session 详解 开发者在线 Builder.com.cn 更新时间:2008-03-23作者&#xff1a;黑暗凝聚力量&#xff0c;堕落方能自由 来源:CSDN 本文关键词&#xff1a; Web开发 ASP session 详解 本文仅代表作者个人观点&#xff0c;正确与否请读者自行研究&#xff01;阅读本文…

[转载] java给对象中的包装类设置默认值

参考链接&#xff1a; Java中的对象类Object 处理方法如下 主要适用于&#xff0c;对象中使用了包装类&#xff0c;但是不能给null需要有默认值的情况 /** * 处理对象中包装类&#xff0c;因为快捷签没有用包装类 * * param object 对象 */ public static void handlePara…

hadoop namenode管理元数据机制

一、简要namenode管理元数据机制&#xff1a; 二、详细namenode管理元数据机制&#xff1a; 三、secondary namenode 合并edits和fsimage&#xff1a; 四、namenode存储元数据细节&#xff1a; 五、checkpoint触发点&#xff1a; 本文转自lzf0530377451CTO博客&#xff0c;原文…

[转载] 多线程详解java.util.concurrent

参考链接&#xff1a; java.lang.Object的灵活性 一、多线程 1、操作系统有两个容易混淆的概念&#xff0c;进程和线程。 进程&#xff1a;一个计算机程序的运行实例&#xff0c;包含了需要执行的指令&#xff1b;有自己的独立地址空间&#xff0c;包含程序内容和数据&#…

BABOK - 企业分析(Enterprise Analysis)概要

描述 企业分析描述我们如何捕捉、提炼并明晰业务需要&#xff0c;并定义一个可能实现这些业务需要的一个方案范围&#xff0c;它包括问题定义和分析&#xff0c;业务案例开发&#xff0c;可行性研究和方案范围定义 目的 明确业务战略需要和目标&#xff0c;并建议方案范围 任务…

6、EIGRP配置实验之负载均衡

1、实验拓扑 2、负载均衡原理 等价负载均衡&#xff1a;默认情况下EIGRP只支持等价负载均衡&#xff0c;默认支持4条线路的等价负载均衡&#xff0c;可以通过show ip protocols 查看&#xff0c;最大可以支持16条线路的等价负载均衡&#xff0c;可以在EIGRP路由进程下通过maxim…

[转载] 详解Java中静态方法

参考链接&#xff1a; Java中的静态类 定义&#xff1a; 在类中使用static修饰的静态方法会随着类的定义而被分配和装载入内存中&#xff1b;而非静态方法属于对象的具体实例&#xff0c;只有在类的对象创建时在对象的内存中才有这个方法的代码段。 注意&#xff1a; 非静态…

[转载] 向集合中添加自定义类型--建议在自定义类型的时候要重写equals方法

参考链接&#xff1a; Java重写equals方法 package com.bjpowernode.t01list; import java.util.ArrayList; /* * 向集合中添加自定义类型 */public class TestList04 { public static void main(String[] args) { ArrayList list new ArrayList(); Student s1 new Stude…

[转载] java重写toString()方法

参考链接&#xff1a; 在Java中重写toString() 前言&#xff1a; 在你兴高采烈的写完一个类&#xff0c;创建测试类时&#xff0c;创建对象&#xff0c;传入参数&#xff0c;调用对象&#xff0c;以为会得到参数值&#xff0c;但突然发现输出的是“ 类名什么东东&#xff1f;&…

haproxy+keepalived实现负载均衡及高可用

HAProxy是一个使用C语言编写的自由及开放源代码软件&#xff0c;其提供高性能性、负载均衡&#xff0c;以及基于TCP和HTTP的应用程序代理。相较与 Nginx&#xff0c;HAProxy 更专注与反向代理&#xff0c;因此它可以支持更多的选项&#xff0c;更精细的控制&#xff0c;更多的健…

[转载] Java中变量与常量

参考链接&#xff1a; Java中的实例变量隐藏 1、变量的定义&#xff1a;定义变量就是要告诉编译器这个变量的数据类型&#xff0c;这样编译器才知道需要分配多少空间给它&#xff0c;以及它能存放什么样的数据。在程序运行过程中空间的值是变化的&#xff0c;这个内存空间就成…

Linux-实用快捷键操作

博文说明【前言】&#xff1a; 本文将通过个人口吻介绍Linux下一些常用的实用快捷键&#xff0c;在目前时间点【2017年6月14号】下&#xff0c;所掌握的技术水平有限&#xff0c;可能会存在不少知识理解不够深入或全面&#xff0c;望大家指出问题共同交流&#xff0c;在后续工作…

iOS技术博客:App备案指南

&#x1f4dd; 摘要 本文介绍了移动应用程序&#xff08;App&#xff09;备案的重要性和流程。备案是规范App开发和运营的必要手段&#xff0c;有助于保护用户权益、维护网络安全和社会秩序。为了帮助开发者更好地了解备案流程&#xff0c;本文提供了一份最新、最全、最详的备…

[转载] Java中静态成员变量,静态代码块,静态内部类何时被初始化?

参考链接&#xff1a; Java中的初始化程序块Initializer Block 关于这个问题&#xff0c;本文不扯理论&#xff0c;直接上代码&#xff0c;通过结果来验证结论&#xff0c;废话少说&#xff0c;测试代码如下&#xff1a; public class StaticTest { public static StaticMem…

mikrotik dhcp server

操作路径: /ip dhcp-server 关联操作: /ip pool属性 述 dhcp server interface (名称) – 选择 DHCP 服务的网络接口 dhcp address space (IP 地址/掩码; 默认: 192.168.0.0/24) – DHCP 服务器将出租给客户端的网络地 址段 gateway (IP 地址; 默认: 0.0.0.0) – 分配给客户端的…

[转载] Java static关键字与static{}语句块

参考链接&#xff1a; Java中的静态块static block 目录直通车 一、 类的加载特性与时机 1、 类加载的特性 2、 类加载的时机 二、 static的三个常用 1、 修饰成员变量 2、 修饰成员方法 3、 静态块&#xff08;static{}&#xff09; 一、 类的加载特性与时机 …

Perl文件读写操作

本文转自 tiger506 51CTO博客&#xff0c;原文链接&#xff1a;http://blog.51cto.com/tiger506/830771&#xff0c;如需转载请自行联系原作者

[转载] Java 语言中的实例初始化块 ( IIB) 详解

参考链接&#xff1a; Java中的实例初始化块(IIB) 在 Java 语言中的类初始化块 文章中我们简单的介绍了下 Java 中的实例初始化块 ( IIB )。不过我觉得介绍的有点简单了&#xff0c;于是&#xff0c;再写一篇文章详细介绍下吧。 Java 语言中&#xff0c;存在三种操作&#x…

不用正则表达式,用javascript从零写一个模板引擎(一)

前言 模板引擎的作用就是将模板渲染成html&#xff0c;html render(template,data)&#xff0c;常见的js模板引擎有Pug,Nunjucks,Mustache等。网上一些制作模板引擎的文章大部分是用正则表达式做一些hack工作&#xff0c;看完能收获的东西很少。本文将使用编译原理那套理论来打…

[转载] Java静态绑定与动态绑定

参考链接&#xff1a; Java中的静态绑定与动态绑定 程序绑定的概念&#xff1a; 绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说&#xff0c;绑定分为静态绑定和动态绑定&#xff1b;或者叫做前期绑定和后期绑定. 静态绑定&#xff1a; 在程序执行前方…