参考链接: 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都不用)。

四. 总结

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

[转载] JAVA泛型杂谈--擦除,协变,逆变,通配符等相关推荐

  1. 泛型型协变逆变_Java泛型类型简介:协变和逆变

    泛型型协变逆变 by Fabian Terh 由Fabian Terh Java泛型类型简介:协变和逆变 (An introduction to generic types in Java: cova ...

  2. 大数据技术之_16_Scala学习_12_设计模式+泛型、上下界、视图界定、上下文界定、协变逆变不变

    大数据技术之_16_Scala学习_12 第十七章 设计模式 17.1 学习设计模式的必要性 17.2 掌握设计模式的层次 17.3 设计模式的介绍 17.4 设计模式的类型 17.5 简单工厂模式( ...

  3. 12:设计模式、泛型、上下界、视图界定、上下文界定、协变逆变不变

    经典的 WordCount 的讲解 示例代码如下: package com.atguigu.chapter14.homework.wordcount/*val lines = List("a ...

  4. 10天学会kotlin DAY7 接口 泛型 协变 逆变

    kotlin 接口 泛型 协变 逆变 前言 1.接口的定义 2.抽象类 3.定义泛型类 4.泛型函数 5.泛型变换 6.泛型类型约束 7.vararg 关键字(动态参数) 8.[] 操作符 9.out ...

  5. C#泛谈 —— 变体(协变/逆变)

    有如下四个类. public class Animal{}public class Mammal : Animal{}public class Dog : Mammal{public void Eat ...

  6. 协变逆变java_Java中的逆变与协变

    什么是逆变与协变 协变(Covariance) 如果B是A的子类,并且F(B)也是F(A)的子类,那么F即为协变 逆变(Contravariance) 如果B是A的子类,并且F(B)成了F(A)的父类 ...

  7. 协变逆变java_Java中的协变与逆变

    Java作为面向对象的典型语言,相比于C++而言,对类的继承和派生有着更简洁的设计(比如单根继承). 在继承派生的过程中,是符合Liskov替换原则(LSP)的.LSP总结起来,就一句话: 所有引用基 ...

  8. Java 泛型总结(三):通配符的使用

    简介 前两篇文章介绍了泛型的基本用法.类型擦除以及泛型数组.在泛型的使用中,还有个重要的东西叫通配符,本文介绍通配符的使用. 这个系列的另外两篇文章: Java 泛型总结(一):基本用法与类型擦除 J ...

  9. Scala语言学习笔记——泛型、上下界、视图界定、上下文界定、协变逆变不变、闭包、柯里化

    1.Scala泛型 应用案例1 /*** @author huleikai* @create 2019-05-27 11:23*/ object TestFanXing {def main(args: ...

最新文章

  1. maven添加本地jar包依赖
  2. VC++程序开机自启动(注册表上注册)
  3. 剑指offer:26-30记录
  4. JDK源码解析之 java.lang.Long
  5. 可逆矩阵的特征值和原来矩阵_线性代数——相似矩阵的可逆变换矩阵P是否唯一...
  6. HDU2019 数列有序!
  7. 单词压缩编码--Trie树
  8. BZOJ 4552 [Tjoi2016Heoi2016]排序 ——线段树 二分答案
  9. 51nod1174--区间中最大的数--线段树
  10. 计算机考试一级b软件未来教育,2019.9全国计算机一级MS Office考试每日一练
  11. ltm4650_LTM4650IY-1APBF_代理全新进口【linear】现货商
  12. 万年历、黄历,获取每日的宜忌、五行、冲煞、值神、彭祖百忌、吉神宜趋、今日胎神、凶神宜忌、二十八星宿、建除十二神
  13. 24_摘录的一些精彩语句1
  14. 【Red Team——基础】通过钓鱼攻击获得访问权限
  15. GetPixel算法
  16. gallary 实现类似viewpage 的效果 左右可见
  17. 安装gtsam遇到的错误
  18. linux运行魔力宝贝,魔力宝贝私服架设(仅供朋友交流学习 绝无商业用途 如有雷同纯熟巧合)...
  19. 【Vue】Vue2生命周期详解
  20. 把WinRAR默认压缩格式换为ZIP

热门文章

  1. 解决“Dynamic Web Module 3.0 requires Java 1.6 or newer.”错误
  2. javaBean和jsp应用
  3. android音量键广播,音量控制键控制的音频流(setVolumeControlStream)描述
  4. oracle授权只读用户,Oracle创建只读用户(账号)的方法
  5. v7000更换电池步骤_ups电源运行中是否可以更换电池?应如何操作呢
  6. 2.2线性表的顺序表
  7. 如何在ps添加箭头_「PS精选案例教程」制作斑驳生锈字体
  8. python爬取哔哩哔哩视频_Python实现视频爬取下载
  9. html自适应_web前端入门到实战:HTML 文档流,设置元素浮动,导致父元素高度无法自适应的解决方法...
  10. python3.8.0安装_Python3.8.0