一文搞懂List 、List<Object>、List<?>的区别以及<? extends T>与<? super T>的区别

前段时间看《Java编程思想》泛型时对 <? extends T>与<? super T>很懵逼,接着看到泛型与集合的更蒙蔽,随后又翻开《码出高效》时,对这些知识点才恍然大悟,发篇博客记录下

List、List<Object>、List<?> 的三者的区别以及 <? extends T>与<? super T> 的区别

List、List<Object>、List<?>

  • List :完全没有类型限制和赋值限定。
  • List<Object> :看似用法与List一样,但是在接受其他泛型赋值时会出现编译错误。
  • List<?>:是一个泛型,在没有赋值前,表示可以接受任何类型的集合赋值,但赋值之后不能往里面随便添加元素,但可以remove和clear,并非immutable(不可变)集合。List<?>一般作为参数来接收外部集合,或者返回一个具体元素类型的集合,也称为通配符集合

代码验证:(感觉用博客的页面插入代码效果不好,在此处贴了图片,代码在文章末尾补上)

<? extends T>与<? super T>

List 最大的问题是只能放置一种类型,如果随意转变类型的话,就是破窗理论,泛型就失去了意义。为了放置多种受泛型约束的类型,出现了 <? extends T>与<? super T> 两种语法。简单来说, <? extends T> 是Get First,适用于,消费集合元素的场景;<? super T>是Put First,适用于,生产集合元素为主的场景。

  • <? extends T> :可以赋值给任意T及T的子类集合,上界为T,取出来的类型带有泛型限制,向上强制转型为T。null 可以表示任何类型,所以null除外,任何元素都不得添加进<? extends T>集合内。
  • <? super T> : 可以复制T及任何T的父类集合,下界为T。再生活中,投票选举类似于<? super T>的操作。选举投票时,你只能往里投票,取数据时,根本不知道时是谁的票,相当于泛型丢失

<? extends T>的场景是put功能受限,而<? super T>的场景是get功能受限

代码示例如下(以加菲猫、猫、动物为例,说明extends和super的详细语法差异0):


在代码第二段中的23行编译时报错:
Type mismatch: cannot convert from List to List<? extends Cat>
因为List赋值给List时会编译出错,因为能赋值给<? extends Cat>的类型,只有Cat和它的子类。因为<? extends Cat>泛型信息表示的是,此笼子(容器)内的全部都是猫,而List笼子(容器)内可能住着毒蛇,鳄鱼、豹猫(猫的天敌)、大型猛禽等动物,不能把它们放同一个笼子(容器)里。 26,27行中,把List赋值给 <? extends T>与<? super T> 都是可以的。

在第三段代码的36、36、37行均编译出错:
The method add(capture#1-of ? extends Cat) in the type List<capture#1-of ? extends Cat> is not applicable for the arguments (Animal)
无法进行add操作,这是因为除null外,任何元素都不能添加进<? extends T>集合中。但<?super Cat>可以往里面添加元素,但只能添加Cat自身即其子类对象,如上面代码中的41、42行。因为猫的笼子中只能关猫,不能关其他动物,如代码中的40行。

在上面代码中的第四段中,所有的 List<? super T>集合 都能执行get方法返回元素,但是泛型丢失,只能返回object对象,如上面代码中的46、47、48行。List<? extends T>集合 可以返回带类型的元素,但只能返回Cat自身及其父类对象,因为子类对象被擦除了,如代码中的50到54行。

附:
第一张图片中的代码

import java.util.ArrayList;
import java.util.List;
public class TestArrayList {
    public static void main(String[] args) {

        //第一段:泛型出现之前集合定义方式
        List a1 =new ArrayList();
        a1.add(new Object());
        a1.add(new Integer(10));
        a1.add(new String("string"));

        //第二段:把a1引用赋值给a2,(a2与a1的区别是增加了泛型限制)
        List<Object> a2 =a1;
        a2.add(new Object());
        a2.add(new Integer(20));
        a2.add(new String("string2"));
        a2.add(25);
        //List<Object> 接受其他泛型赋值时,会报异常(因为在下面例子中List<Integer>不能转为List<Object>)
        List<Integer> aint = new ArrayList<Integer>();
        List<Object> a22 =aint;//Type mismatch: cannot convert from List<Integer> to List<Object>

        //第三段:把a1引用赋值给a3,(a3与a1的区别是增加了泛型<Integer>)
        List<Integer> a3 = a1; //此时如果遍历a3则会报类型转换异常ClassCastException
        a3.add(new Integer(20));
        //下面两行编译出错,不允许增加非Integer类型进入集合
        a3.add(new Object());//The method add(Integer) in the type List<Integer> is not applicable for the arguments (Object)
        a3.add(new String("string2"));

        //第四段:把a1引用赋值给a4,a4与a1的区别是增加了通配符
        List<?> a4 = a1;
        //允许删除和清除元素
        a4.remove(0);
        a4.clear();
        //编译错误,不允许添加任何元素
        a4.add(new Object());//The method add(capture#3-of ?) in the type List<capture#3-of ?> is not applicable for the arguments (Object)
        a4.add(new Integer(20));
        a4.add(new String("string2"));
    }
}

第二张图片中的代码

import java.util.ArrayList;
import java.util.List;
class Animal{}
class Cat extends Animal{}
class Garfield extends Cat{}

//用动物,猫,加菲猫的继承关系说明extends与super在集合中的意义
public class AnimalCatGarfield {
    public static void main(String[] args) {
        //第一段:声明第三个依次继承的集合:Object>动物>猫>加菲猫  三个泛型集合可以理解为三个不同的笼子
        List<Animal> animal = new ArrayList<Animal>();        //动物
        List<Cat> cat = new ArrayList<Cat>();                 //猫
        List<Garfield> garfield = new ArrayList<Garfield>();  //加菲猫

        animal.add(new Animal());
        cat.add(new Cat());
        garfield.add(new Garfield());

        //第二段:测试赋值操作  以Cat为核心,因为它既有子类又有父类
        //下行编译出错。只能赋值Cat或Cat子类集合
        List<? extends Cat> extendsCatFromAnimal = animal;
        List<? super Cat> superCatFromAnimal = animal;

        List<? extends Cat> extendsCatFromCat = cat;
        List<? super Cat> superCatFromCat = cat;

        List<? extends Cat> extendsCatFromGarfield = garfield;
        //下行编译出错。只能赋值Cat或着Cat父类集合
        List<? super Cat> superCatFromGarfield = garfield;

        //第三段:测试add方法
        //下面三行中所有的<? extends T>都无法进行add操作,编译出错
        extendsCatFromCat.add(new Animal());
        extendsCatFromCat.add(new Cat());
        extendsCatFromCat.add(new Garfield());

        //下行编译出错。只能添加Cat或者Cat的子类集合。
        superCatFromCat.add(new Animal());
        superCatFromCat.add(new Cat());
        superCatFromCat.add(new Garfield());

        //第四段:测试get方法
        //所有的super操作能够返回元素,但是泛型丢失,只能返回object对象
        Object object1 = superCatFromCat.get(0);
        Animal object = superCatFromCat.get(0);//Type mismatch: cannot convert from capture#8-of ? super Cat to Animal
        Cat object3 = superCatFromCat.get(0);//
        //以下extends操作能够返回元素
        Animal catExtends3 = extendsCatFromCat.get(0);
        Object catExtends2 = extendsCatFromCat.get(0);
        Cat catExtends1 = extendsCatFromCat.get(0);
        //下行编译错误。虽然Cat集合从Garfield赋值而来,但类型擦除后,是不知道的
        Garfield cat2 = extendsCatFromGarfield.get(0);
    }
}

原文地址:https://www.cnblogs.com/minghaiJ/p/11259346.html

时间: 2024-10-28 19:51:01

一文搞懂List 、List<Object>、List<?>的区别以及<? extends T>与<? super T>的区别的相关文章

夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!

目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接口与抽象类的本质区别是什么? 基本语法区别 设计思想区别 如何回答面试题:接口和抽象类的区别? 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl

【Data Visual】一文搞懂matplotlib数据可视化

一文搞懂matplotlib数据可视化 作者:白宁超 2017年7月19日09:09:07 摘要:数据可视化主要旨在借助于图形化手段,清晰有效地传达与沟通信息.但是,这并不就意味着数据可视化就一定因为要实现其功能用途而令人感到枯燥乏味,或者是为了看上去绚丽多彩而显得极端复杂.为了有效地传达思想概念,美学形式与功能需要齐头并进,通过直观地传达关键的方面与特征,从而实现对于相当稀疏而又复杂的数据集的深入洞察.然而,设计人员往往并不能很好地把握设计与功能之间的平衡,从而创造出华而不实的数据可视化形式,

一文搞懂 RSA 算法

一文搞懂 RSA 算法 地球上最重要的算法 如果没有 RSA 算法,现在的网络世界毫无安全可言,也不可能有现在的网上交易.上一篇文章 ssh 协议为什么安全 中的 ssh 协议也是基于 RSA 加密算法才能确保通讯是加密的,可靠的. 1976年以前,所有的加密方法都使用对称加密算法:加密和解密使用同一套规则.例如:甲使用密钥 A 加密,将密文传递给乙,乙仍使用密钥 A 解密.如果密钥 A 在甲传递给乙的过程中泄露,或者根据已知的几次密文和明文推导出密钥 A,则甲乙之间的通讯将毫无秘密. 1976

一文搞懂蓝绿发布、灰度发布和滚动发布(转)

应用程序升级面临最大挑战是新旧业务切换,将软件从测试的最后阶段带到生产环境,同时要保证系统不间断提供服务. 长期以来,业务升级渐渐形成了几个发布策略:蓝绿发布.灰度发布和滚动发布,目的是尽可能避免因发布导致的流量丢失或服务不可用问题. 一. 蓝绿发布 项目逻辑上分为AB组,在项目系统时,首先把A组从负载均衡中摘除,进行新版本的部署.B组仍然继续提供服务. 当A组升级完毕,负载均衡重新接入A组,再把B组从负载列表中摘除,进行新版本的部署.A组重新提供服务. 最后,B组也升级完成,负载均衡重新接入B

一文搞懂vim复制粘贴

转载自本人独立博客https://liushiming.cn/2020/01/18/copy-and-paste-in-vim/ 概述 复制粘贴是文本编辑最常用的功能,但是在vim中复制粘贴还是有点麻烦的,有一点学习成本.本文总结了使用vim复制粘贴的典型场景和使用方法,希望对读者有帮助. vim内部复制.粘贴.剪切 选择文本 v+光标移动 (按字符选择)高亮选中所要的文本,然后进行各种操作(比如,d表示删除). V (按行选择) v+选中的内容+c 更改选中的文字 复制:y(ank) y 用v

一文搞懂*argv和**kwargs

大多刚接触Python的学员,会对*argv和**kwargs这两个魔法变量的用法产生困惑,今天本文将全面梳理下这两个小可爱的使用方法,让各位同学彻底搞懂它们的规则. 这两个变量主要用户函数定义,有了它俩你可以将不定数量的参数传递给一个函数.这里,需要申明下:在写Python代码时,我们习惯将它俩写成*argv和**kwargs,这只是一个书写习惯,你也可以任性地用*var和**kvars来代替它们,要注意的是符号*和**是必须写的. 函数在实际执行时,编程人员无法预先知道会被传入多少实参,这是

一文搞懂 Java 线程中断

在之前的一文<如何"优雅"地终止一个线程>中详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程的方法吗?答案是肯定的,它就是我们今天要分享的--线程中断. 下面的这断代码大家应该再熟悉不过了,线程休眠需要捕获或者抛出线程中断异常,也就是你在睡觉的时候突然有个人冲进来把你吵醒了. try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }

Java虚拟机系列一:一文搞懂 JVM 架构和运行时数据区

前言 之前写博客一直比较随性,主题也很随意,就是想到什么写什么,对什么感兴趣就写什么.虽然写起来无拘无束,自在随意,但也带来了一些问题,每次写完一篇后就要去纠结下一篇到底写什么,看来选择太多也不是好事儿,更重要的是不成体系的内容对读者也不够友好.所以以后的博客尽量按系列来写,不过偶尔也会穿插其他的内容.接下来一段时间我会把写博客的重点放在 JVM (Java Virtual Machine) 和 JUC (java util concurrent ) 上,对 Java 虚拟机和 Java 并发编

一文搞懂 ThreadLocal 原理

当多线程访问共享可变数据时,涉及到线程间同步的问题,并不是所有时候,都要用到共享数据,所以就需要线程封闭出场了. 数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭. 本文主要介绍线程封闭中的其中一种体现:ThreadLocal,将会介绍什么是 ThreadLocal:从 ThreadLocal 源码角度分析,最后介绍 ThreadLocal 的应用场景. 什么是 ThreadLocal? ThreadLocal 是 Java 里一种特殊变量,