在上篇《Java泛型的协变》这篇文章中遗留以下问题——协变不能解决将子类型添加到父类型的泛型列表中。本篇将用逆变来解决这个问题。

实验准备

  我们首先增加以下方法,见代码清单1所示。

代码清单1

    /**
*
* 描 述:Exp3使用逆变<br/>
* 作 者:jiaan.gja<br/>
* 历 史: (版本) 作者 时间 注释 <br/>
* @param itemList
*/
public void doDecorate3(List<? super T> itemList, T t) {
for(int i = 0; i < itemList.size(); i++) {
System.out.println(itemList.get(i));
}
} /**
*
* 描 述:Exp3使用逆变<br/>
* 作 者:jiaan.gja<br/>
* 历 史: (版本) 作者 时间 注释 <br/>
* @param itemList
*/
public void addDecorate3(List<? super T> itemList, T t) {
itemList.add(t);
}

语法List<? super T>即为Java泛型的逆变,addDecorate3方法中的itemList.add(t)语句也没有协变出现的编译问题。

实验:泛型逆变

  现在我们尝试下逆变的用途,如代码清单2所示。

代码清单2

/**
*
* 类 名: Exp3<br/>
* 描 述: 泛型的逆变使用<br/>
* 作 者: 耿嘉安<br/>
* 创 建: 2015年8月25日<br/>
*
* 历 史: (版本) 作者 时间 注释
*/
class Exp3 {
public static void main(String[] args) { Decorator<Auction> decoratorA = new Decorator<Auction>();
List<Auction> listA = new ArrayList<Auction>();
Auction auctionOne = new Auction("auctionOne");
Auction auctionTwo = new Auction("auctionTwo");
decoratorA.addDecorate3(listA, auctionOne);
decoratorA.addDecorate3(listA, auctionTwo);
decoratorA.doDecorate3(listA); Decorator<Table> decoratorB = new Decorator<Table>();
List<Table> listB = new ArrayList<Table>();
Table tableOne = new Table("tableOne", 10);
Table tableTwo = new Table("tableTwo", 20);
decoratorB.addDecorate3(listB, tableOne);
decoratorB.addDecorate3(listB, tableTwo);
decoratorB.doDecorate3(listB); Decorator<Service> decoratorC = new Decorator<Service>();
List<Service> listC = new ArrayList<Service>();
Service serviceOne = new Service("serviceOne", "methodOne");
Service serviceTwo = new Service("serviceTwo", "methodTwo");
decoratorC.addDecorate3(listC, serviceOne);
decoratorC.addDecorate3(listC, serviceTwo);
decoratorC.doDecorate3(listC); /**
* 测试逆变开始
*/
decoratorA.doDecorate3(listB);
decoratorA.doDecorate3(listC); }
}

我们发现以下两条语句都会编译出错:

        decoratorA.doDecorate3(listB);
decoratorA.doDecorate3(listC);

我们暂且将这个问题放一放,将它们暂时注释掉,增加代码清单3中的代码。

代码清单3

        List<Object> listD = new ArrayList<Object>();

        decoratorA.doDecorate3(listD);
decoratorA.doDecorate3(listA);
decoratorA.doDecorate3(listB);
decoratorA.doDecorate3(listC);
decoratorB.doDecorate3(listD);
decoratorB.doDecorate3(listA);
decoratorB.doDecorate3(listB);
decoratorB.doDecorate3(listC);
decoratorC.doDecorate3(listD);
decoratorC.doDecorate3(listA);
decoratorC.doDecorate3(listC);
decoratorC.doDecorate3(listB);

代码清单3中,decoratorA.doDecorate3(listB);decoratorA.doDecorate3(listC);decoratorB.doDecorate3(listC);decoratorC.doDecorate3(listB);这四条语句编译错误,这说明如下声明是允许的:

List<? super Auction> itemList = new ArrayList<Object>();
List<? super Auction> itemList = new ArrayList<Auction>();
List<? super Table> itemList = new ArrayList<Object>();
List<? super Table> itemList = new ArrayList<Auction>();
List<? super Table> itemList = new ArrayList<Table>();
List<? super Service> itemList = new ArrayList<Object>();
List<? super Service> itemList = new ArrayList<Auction>();
List<? super Service> itemList = new ArrayList<Service>();

而下面这样是不允许的:

List<? super Auction> itemList = new ArrayList<Table>();
List<? super Auction> itemList = new ArrayList<Service>();
List<? super Table> itemList = new ArrayList<Service>();
List<? super Service> itemList = new ArrayList<Table>();

现在我们编写以下代码:

        List<? super Auction> itemList = listA;
Object o = itemList.get(0);
Auction a = itemList.get(0);
Table t = itemList.get(0);
Service s = itemList.get(0);
itemList = listD;
o = itemList.get(0);
a = itemList.get(0);
t = itemList.get(0);
s = itemList.get(0);

上述代码中,除了从itemList获取Object的语句之外,都会编译出错,这是为什么?因为itemList有可能是一个ArrayList<Object>,所以转型为Auction是可能错误的。itemList可能是ArrayList<Object>或者ArrayList<Auction>,所以转型为Table或者Service必然是不对的。

最后我们增加以下代码:

        itemList.add(new Auction("auctionThr"));
itemList.add(new Service("serviceThr", "methodThr"));
itemList.add(new Table("tableThr", 10));

上述代码没有问题,因为不论itemList是ArrayList<Object>或者ArrayList<Auction>,向其中添加Auction、Table、Service都符合最初的原则。

假如向itemList添加Object是编译失败的,因为itemList可能是ArrayList<Auction>。

总结

如果向泛型中添加子类,可以使用逆变来实现。如果要从泛型中获取子类,逆变有转型错误,此时应该使用协变。

Java泛型的逆变的更多相关文章

  1. Java中的逆变与协变

    看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...

  2. Java中的逆变与协变(转)

    看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...

  3. Java中的逆变与协变 专题

    结论先行: PECS总结: 要从泛型类取数据时,用extends: 协变 要往泛型类写数据时,用super: 逆变 既要取又要写,就不用通配符(即extends与super都不用) 不变 List&l ...

  4. 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4

    前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...

  5. ios开发ios9新特性关键字学习:泛型,逆变,协变,__kindof

    一:如何去学习?都去学习什么? 1:学习优秀项目的设计思想,多问几个为什么,为什么要这么设计,这么设计的好处是什么,还能不能在优化 ,如何应用到自己的项目中 2:学习优秀项目的代码风格,代码的封装设计 ...

  6. Java协变、逆变、类型擦除

    协变.逆变 定义 Java中String类型是继承自Object的,姑且记做String ≦ Object,表示String是Object的子类型,String的对象可以赋给Object的对象.而Ob ...

  7. Java中的逆变与协变 很直接不饶弯的讲出来了

    ```java 协变 extends只能new 辈分比自己低的家伙 List<? extends Number> list001 = new ArrayList<Integer> ...

  8. .NET Core CSharp初级篇 1-8泛型、逆变与协变

    .NET Core CSharp初级篇 1-8 本节内容为泛型 为什么需要泛型 泛型是一个非常有趣的东西,他的出现对于减少代码复用率有了很大的帮助.比如说遇到两个模块的功能非常相似,只是一个是处理in ...

  9. JAVA泛型——逆变

    在上篇<JAVA泛型——协变>这篇文章中遗留以下问题——协变不能解决将子类型添加到父类型的泛型列表中.本篇将用逆变来解决这个问题. 实验准备 我们首先增加以下方法,见代码清单1所示. 代码 ...

随机推荐

  1. javascript做的一个根据table中某个td的值为日期时的倒计时

    JavaScript代码: <script> window.onload = window.onload = function () { getTdValue(); } //根据传过来的天 ...

  2. .NET中的异常处理机制(一)

    1.异常处理的总体指导思想 学习C#中的异常处理机制,大概要了解以下几点: 首先,我们需要知道的事所有具体异常都是继承自System.Exception基类的. 其次,要熟悉FCL类库内置好的一些异常 ...

  3. Asp.NetCore取配置信息

    本文简单介绍Asp.NetCore取自定义配置信息的方法,要取配置信息首先得有配置文件. 1, 本例新建了一个TimeOut.json配置文件,和其对应的一个TimeOut类 2, 首先在Startu ...

  4. 当Shell遇上了NodeJS

    此文已由作者尧飘海授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 摘要 在企业级系统维护和互联网运维中,Shell脚本的编写与维护常必不可少, 但是Shell脚本的编写与调试 ...

  5. JVM垃圾收集器(2)

    此文已由作者赵计刚薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1.G1 说明: 从上图来看,G1与CMS相比,仅在最后的"筛选回收"部分不同(CMS ...

  6. Delphi XE7实现的任意位置弹出菜单

    Delphi XE7中目前还没有弹出菜单组件,这个弹出菜单应用很普遍,在JAVA开发的安卓程序中很简单就可以用上了,应该说是一个标准控件.看了一些例子,但是都不能满足我想在任意位置弹出菜单需求,于是自 ...

  7. DOS目录相关命令

    MD----创建自目录命令   格式:MD[盘符:][路径名]<子目录名> 1)在C盘的根目录下创建名为FOX的子目录  C:\>MD FOX 2)在FOX子目录下创建USER子目录 ...

  8. “全栈2019”Java第四章:创建第一个Java程序

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. java中数组的插入

    package com.hxzy.demo; import java.util.Arrays;import java.util.Scanner; public class Demo1 { public ...

  10. poj 3133 Manhattan Wiring

    http://poj.org/problem?id=3133 考虑插头 dp 用四进制表示一个插头的状态,0 表示没有插头,2 表示这个插头是连接两个 2 的,3 同理 然后就是大力分类讨论了 这题还 ...