当前位置: 首页 > 编程日记 > 正文

Java中? extends T和? super T的理解

? 通配符类型

- <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类;
<? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object;

上界<? extends T>不能往里存,只能往外取

比如,我们现在定义:List<? extends T>首先你很容易误解它为继承于T的所有类的集合,你可能认为,你定义的这个List可以用来put任何T的子类,那么我们看下面的代码:

import java.util.LinkedList;
import java.util.List;public class test { public static void main(String[] args) { List<? extends Father> list = new LinkedList<>(); list.add(new Son()); } } class Human{ } class Father extends Human{ } class Son extends Father{ } class LeiFeng extends Father { }

list.add(new Son());这行会报错:The method put(Son) is undefined for the type List<capture#1-of ? extends Father>

List<? extends Father> 表示 “具有任何从Son继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象。可以添加null,因为null 可以表示任何类型。所以List 的add 方法不能添加任何有意义的元素,但是可以接受现有的子类型List 赋值。

你也许试图这样做:

List<? extends Father> list = new LinkedList<Son>();
list.add(new Son());

即使你指明了为Son类型,也不能用add方法添加一个Son对象。

list中为什么不能加入Father类和Father类的子类呢,我们来分析下。

List<? extends Father>表示上限是Father,下面这样的赋值都是合法的

   List<? extends Father> list1 = new ArrayList<Father>();List<? extends Father> list2 = new ArrayList<Son>(); List<? extends Father> list3 = new ArrayList<LeiFeng>();

如果List<? extends Father>支持add方法的话:

  • list1可以add Father和所有Father的子类;
  • list2可以add Son和所有Son的子类;
  • list3可以add LeiFeng和所有LeiFeng的子类。

下面代码是编译不通过的:

list1.add(new Father());//error
list1.add(new Son());//error

原因是编译器只知道容器内是Father或者它的派生类,但具体是什么类型不知道。可能是Father?可能是Son?也可能是LeiFeng,XiaoMing?编译器在看到后面用Father赋值以后,集合里并没有限定参数类型是“Father“。而是标上一个占位符:CAP#1,来表示捕获一个Father或Father的子类,具体是什么类不知道,代号CAP#1。然后无论是想往里插入Son或者LeiFeng或者Father编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。

所以通配符<?>和类型参数的区别就在于,对编译器来说所有的T都代表同一种类型。比如下面这个泛型方法里,三个T都指代同一个类型,要么都是String,要么都是Integer。

public <T> List<T> fill(T... t);

但通配符<?>没有这种约束,List<?>单纯的就表示:集合里放了一个东西,是什么我不知道。

所以这里的错误就在这里,List<? extends Father>里什么都放不进去。

List<? extends Father> list不能进行add,但是,这种形式还是很有用的,虽然不能使用add方法,但是可以在初始化的时候一个Season指定不同的类型。比如:

List<? extends Father> list1 = getFatherList();//getFatherList方法会返回一个Father的子类的list

另外,由于我们已经保证了List中保存的是Father类或者他的某一个子类,所以,可以用get方法直接获得值:

List<? extends Father> list1 = new ArrayList<>();
Father father = list1.get(0);//读取出来的东西只能存放在Father或它的基类里。
Object object = list1.get(0);//读取出来的东西只能存放在Father或它的基类里。 Human human = list1.get(0);//读取出来的东西只能存放在Father或它的基类里。 Son son = (Son)list1.get(0);

下界

//super只能添加Father和Father的子类,不能添加Father的父类,读取出来的东西只能存放在Object类里
List<? super Father> list = new ArrayList<>(); list.add(new Father()); list.add(new Human());//compile error list.add(new Son()); Father person1 = list.get(0);//compile error Son son = list.get(0);//compile error Object object1 = list.get(0);

因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是Father的基类,那往里存粒度比Father小的都可以。出于对类型安全的考虑,我们可以加入Father对象或者其任何子类(如Son)对象,但由于编译器并不知道List的内容究竟是Father的哪个超类,因此不允许加入特定的任何超类(如Human)。而当我们读取的时候,编译器在不知道是什么类型的情况下只能返回Object对象,因为Object是任何Java类的最终祖先类。但这样的话,元素的类型信息就全部丢失了。

PECS原则

最后看一下什么是PECS(Producer Extends Consumer Super)原则,已经很好理解了:

  • 频繁往外读取内容的,适合用上界Extends。
  • 经常往里插入的,适合用下界Super。

总结

  • extends 可用于返回类型限定,不能用于参数类型限定(换句话说:? extends xxx 只能用于方法返回类型限定,jdk能够确定此类的最小继承边界为xxx,只要是这个类的父类都能接收,但是传入参数无法确定具体类型,只能接受null的传入)。
  • super 可用于参数类型限定,不能用于返回类型限定(换句话说:? supper xxx 只能用于方法传参,因为jdk能够确定传入为xxx的子类,返回只能用Object类接收)。
  • ? 既不能用于方法参数传入,也不能用于方法返回。


<? extends SomeClass>与<T extends SomeClass>的区别

看apache parquet源码时,发现代码各种泛型嵌套,有必要系统整理一下关于泛型的各种知识,在此做一总结。

首先是名词对应表,不需要记住右边的名字,但需要知道左边的各种用法

List<String> —- 参数化的类型 
List<E> —- 泛型 
List<?> —- 无限制通配符类型 
<E extends SomeClass> —- 有限制类型参数 
List <? extends SomeClass>—- 有限制通配符类型 
<T extends Comparable<T>> —– 递归类型限制 
static <E> List<E> asList(E[] a) —- 泛型方法

下面自己的实验包括代码,标号1是解决题目里描述的问题,其余的标号也是自己遇到的一些关键的问题。

疑问&要解释的东西


1:<E extends ClassA> 与 <? extends ClassA>有什么区别?

  • 答:当我第一次接触这两名词时,感觉他们的功能是一样的,T可以代表任意的子类,?也可以代表任意的子类。 
    首先我们明确一下两边的名字,限制类型 & 通配符类型,<E extends ClassA>表示后续都只能使用E进行某些判断或操作,而<? extends ClassA>?表示后续使用时可以是任意的。 
    举个<E extends ClassA>最常见的例子,用于比较操作,比如返回“最大值”,“最大值”的定义为:整型、浮点型返回最大值,字符串返回字典序最大者,由于想调用compareTo函数,我们让所有参数都继承Compareble,即T extends Comparable<T>,整个测试代码如下
package test;/***  定义了 <T extends someClass>, *  里面的代码便只能用somClass的子类T进行比较或其他操作。*/
public class MaximumTest { // determines the largest of three Comparable objects public static <T extends Comparable<T>> T maximum(T x, T y, T z) { T max = x; // assume x is initially the largest if ( y.compareTo( max ) > 0 ) { max = y; // y is the largest so far } if ( z.compareTo( max ) > 0 ) { max = z; // z is the largest now } return max; // returns the largest object } public static void main(String args[]) { System.out.printf( "Max of %d, %d and %d is %d\n\n", 3, 4, 5, maximum( 3, 4, 5 ) ); System.out.printf( "Maxm of %.1f,%.1f and %.1f is %.1f\n\n", 6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) ); System.out.printf( "Max of %s, %s and %s is %s\n","pear", "apple", "orange", maximum( "pear", "apple", "orange" ) ); } }

注释里已经写清楚了,我们只能用T类型来进行一些操作,我们不能把T替换成?,因为?并不是一个类名,它只是一个通配符,然后举个<? extends ClassA>的例子。

比如我们有一个Stack类,类里提供一个pullAll方法,我们想把一系列元素全部放到堆栈中,如下方法

   // Stack定义public class Stack<E> {public Stack(); public E pop(); public boolean isEmpty(); } // ... public <E> void pushAll(Iterable<E> src) { for(E e : src) { push(e); } }

这个方法编译时没问题,Iterable src的元素类型与堆栈的类型完全匹配就没有问题。但是假如有一个Stack<Number>调用了push(intVal),这里的intVal是Integer类型,这是可以的,因为Integer是Number的一个子类型,但下面的代码会报编译错误,

Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = "..."; numberStack.pushAll(integers);

因为在Java中,参数化类型是不可变的。所以现在我们的通配符类型就派上用场了,代码如下

public void pushAll(Iterable<? extends E> scr) {for( E e : src) {push(e);}
}  

此处就必须用通配符?,代表泛型的泛指“E的某个子类型的Iterator接口”。 
扩张阅读:Stackoverflow : 区别


2、List<Object> o = new ArrayList<Long>(); 报错

  • 不同于数组Object[] o,Long[] o,因为List<Type1>List<Type2>不互为子类型or超类型

3、无法创建泛型数组。

  • 自己以前发的一片文章里由Cannot create a generic array of ArrayList引出的学习–Java范型就有这个,当时解释的有点瑕疵,<<Effective Java>> 第二版 106页举了一个例子来说明这样的不安全性。

4、泛型方法的使用

    • static <E> List<E> asList(E[] a) —- 泛型方法 
      参考这篇文章Java中的泛型方法的解释,注意区分Class与实例。

转载于:https://www.cnblogs.com/chenxibobo/p/9655236.html

相关文章:

学习Modern UI for WPF

这两天断断续续的学了学Modern UI for WPF 没啥学习笔记呵呵&#xff0c;来自大牛王春明的博客园 http://www.cnblogs.com/wangchunming/category/342887.html 此大牛学习范围之广 成果之丰富 着实是学习的典范转载于:https://www.cnblogs.com/DragonX/p/3146818.html

idea的tomcat配置文件在哪里修改_MyBatis配置文件详解

MyBatis 的配置文件包含了会影响 MyBatis 行为的设置和属性信息&#xff0c;决定了mybatis的运行轨迹&#xff0c;能充分了解这些配置的以及配置所带来的的影响&#xff0c;你就是大神&#xff01;配置文件的根节点是configuration&#xff0c;他的子孙节点有&#xff1a;prope…

《几何与代数导引》例1.4——定比分点

点$r$分有向线段$\vec{pq}$成定比$k$,即$\vec{pr}k\vec{rq}(k\neq-1)$.在仿射标架中&#xff0c;已知$p(a_1,a_2,a_3)$,$q(b_1,b_2,b_3)$和$k$,求$r(c_1,c_2,c_3)$解:由于$c_i-a_ik(b_i-c_i)$,因此$c_i\frac{kb_ia_i}{1k}$.转载于:https://www.cnblogs.com/yeluqing/archive/20…

C#第一个程序Helloworld

转载于:https://www.cnblogs.com/gzhbk/p/9656149.html

Leanote

https://github.com/leanote/leanote/wiki/Leanote-%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B—-Mac-and-Linux 安装的网址 我们相当于是在本地建立了一个服务器&#xff0c;然后将我们的leanote部署上去了 我们这里的启…

微软安全新闻聚焦-双周刊第三十四期

Biweekly Spotlights 2013. 6. 5– 2013. 6. 20 第 34 期 微软发布 EMET 4.0 2013年6月17日 Enhanced Mitigation Experience Toolkit(EMET) 是微软提供的一个免费的攻击防御工具&#xff0c;它依托 Windows 系统本身的防御机制来阻止攻击者对各类…

mysql如何实现实时存储_OpenResty + Mysql 实现日志实时存储

应用场景和日志文件解析本配置主要解决 Nginx 向 MySQL 中实时插入日志的问题&#xff0c;采用 OpenResty Mysql 实现。1. 刚开始的时候看了 Nginx 和 MySQL 的连接模块。比如说 nginx-mysql-module&#xff0c;可以连接 MySQL。但是插入日志时遇到问题&#xff0c;我们知道 n…

简易RS232 建模二 (接收)

//clK系统时钟为50MHZ 先发低位后发高位 先接收地位后接收高位module uart_tx (input clk,rst_n, UART_CTS, output reg UART_RTS, input UART_RXD, output reg UART_TXD, output [7:0] led);reg [3:0]state;reg [30:0] count;reg [7:0] data;assign led rx_data; reg [7:0] …

《UNIX高级环境编程》 -- apue.h

在看《UNIX高级环境编程》这本书的时候&#xff0c;会遇到一个问题就是这个”apue.h”,这个是作者为了编写代码方便封装了一个库&#xff0c;我们可以使用下面的方式解决这个问题&#xff0c;让我们的代码可以像作者一样去使用&#xff0c;这样的话&#xff0c;我们就可以好好研…

mysql 多少个数据库_mysql数据库的几个基本概念

1、表数据库表是一系列二维数组的集合&#xff0c;用来存储数据和操作数据的逻辑结构。行是记录&#xff0c;列是字段&#xff0c;每一列表示记录的一个属性&#xff0c;都有相应的描述信息。2、数据类型&#xff1a;数据类型决定了数据在计算机中的存储格式&#xff0c;代表不…

教孩子正确对待分数

期末考试各科成绩渐渐公布了&#xff0c;小丽迫不及待地跑到办公室向老师打听分数。看到自己那一科成绩好时欣喜若狂&#xff0c;看到差的则懊悔不已。如果你的孩子也象小丽一样视考分为命根&#xff0c;该如何教孩子正确对待分数?让她从考分的困惑中解放出来?●考前给孩子制…

canvas初尝试

最近学习了canvas&#xff0c;就拿它做了这么个小东西&#xff0c;感觉已经爱上canvas了。上代码 /* * auhor : 开发部-前端组-李鑫超 * property { tableData : {Array} 表格数据 add v-1.0.0 } * property { columData : {Array} 表头数据 add v-1.0.0 } * property { me…

模板1.0 -- 模板基本原理

为什么需要模板 我们经常有这样的一种使用的情形&#xff0c;就是我们可能需要设计一个函数&#xff0c;然后函数的参数可能是整形的&#xff0c;也可能是浮点型的&#xff0c;还有可能是其他的类型的&#xff0c;这个时候如果对于每一个类型都写一个函数&#xff0c;未免有点…

[置顶] 我的GB28181标准开发里程碑——基于eXosip的IPC端与SPVMN注册成功

昨天编译搭建好eXosip的开发环境后&#xff0c;今天完成了SIP注册功能&#xff0c;里程碑一战啊&#xff01;加油加油&#xff0c;成功就在眼前&#xff01; 今天基于eXosip做了一个IPC客户端&#xff0c;成功与公安部的SPVMN视频监控联网调测软件自测工具进行注册交互&#xf…

mysql如何避免特殊字符查询_如何避免MySQL中的特殊字符?

慕的地10843我已经用Java开发了自己的MySQL转义方法(如果对任何人都有用的话)。请参阅下面的类代码。警告&#xff1a;如果启用了任何_反斜杠_转义SQL模式&#xff0c;则出错。private static final HashMap sqlTokens;private static Pattern sqlTokenPattern;static{ …

visio 2010 修改 默认字体 字号大小 方法

哈哈&#xff0c;我这是标题党&#xff0c;先给大家泼个冷水。Visio2010 并不支持对一次性地修改绘图中所有图形的字体大小&#xff01;但可以有一个比较笨的方法解决。1.新建一个模具2.将常用的图形放到这个模具中3.对每个图形进行编辑4.对这个形状的字体&#xff0c;字号进行…

[BZOJ3329] Xorequ

题解&#xff1a; 网上的方法基本是建立在发现临位不能相等的基础上的 这个很好证。。 但是不利用这个特征也是可以的 x^2x3x 我们考虑二进制的前i位&#xff0c;我们会发现3x最多涉及到了前i2位 于是我们可以记录一下前i位的3x的i1,i2位的状态&#xff0c;以及第i位填了什么 因…

Asp.net中时间格式化的几种方法

1. 数据控件绑定时格式化日期方法:<asp:BoundColumn DataField"AddTime" HeaderText"添加时间" DataFormatString"{0:yyyy-MM-dd HH:mm}></asp:BoundColumn><asp:BoundField DataField"AddTime" HeaderText"添加时间&q…

centos设置网络自动启动

问题描述 centos7虚拟机如何设置开机自启动网络设置 解决方法 切换到root用户进入到网络设置的目录下面cd /etc/sysconfig/network-scripts/当前目录下面有一个类似于ifcfg-ens33&#xff0c;使用vim打开文件进行编辑&#xff0c;将ONBOOTno修改成为yes就可以了

mysql删除原则_MySQL数据库的增删选查

数据库是专门存储数据对象的容器&#xff0c;这里的数据对象包括表、视图、触发器、存储过程等&#xff0c;其中表是最基本的数据对象。创建数据库在 MySQL 数据库中存储数据对象之前&#xff0c;先要创建好数据库。语法&#xff1a;create database [if not exists] [[default…

ubuntu网卡配置

1、dhcp自动获取 sudo vi /etc/network/interfaces auto eth0 iface eth0 inet dhcp 设置生效: sudo /etc/init.d/networking restart 或 sudo dhclient eth0 2、静态IP地址 sudo vi /etc/network/interfaces auto eth0 iface eth0 inet static address 192.168.3.90 netmask 2…

自动化运维工具----ansible

自动化运维工具----ansible ansible是新出现的运维工具是基于Python研发的糅合了众多老牌运维工具的优点实现了批量操作系统配置、批量程序的部署、批量运行命令等功能。 主要模块以及功能&#xff1a; 1 command 2 user 3 group 4 cron 5 copy 6 file 7 ping 8 yum 9 service …

【内核】嵌入式linux内核的五个子系统

Perface Linux内核主要由进程调度&#xff08;SCHED&#xff09;、内存管理&#xff08;MM&#xff09;、虚拟文件系统&#xff08;VFS&#xff09;、网络接口&#xff08;NET&#xff09;和进程间通信&#xff08;IPC&#xff09;5个子系统组成&#xff0c;如图1所示。 图1 Li…

git用户文档1 — git基础

1. git基础 1.1 分布式 我们把远端仓库(云端的仓库)称为repo&#xff0c;repo必须有一个master分支&#xff0c;就是主分支。 repo除了有一个master分支&#xff0c;还有很多其他的分支&#xff0c;若干个分支之间存储的数据一版都是不一样的本地可以git clone下来repo的mast…

MySQL5.7的date类型_Mysql5.7 虚拟列数据类型为DATE时,如何存入数据?

表结构&#xff1a;v_date为虚拟列CREATE TABLE test ( json TEXT NULL, date DATETIME NULL DEFAULT NULL, v_date DATE AS (json_extract(json,$.date)) VIRTUAL)COMMENT测试表\r\nCOLLATEutf8mb4_general_ciENGINEInnoDB;插入&#xff1a;INSERT INTO test (json) …

找出字符串中所有数字

刚才网友在SKYPE问Insus.NET一个问题&#xff0c;在MS SQL中&#xff0c;怎样找出一个字符串所有数字。 Insus.NET使用较简单与平常的方法&#xff0c;就是使用循环的方法&#xff0c;循环字符串中每一个字符&#xff0c;并插入至一个表变量中。然后再SELECT这个表变量&#xf…

Safair 浏览器cllick事件不生效或者需要双击才生效

针对Safair 浏览器cllick事件不生效或者需要双击才生效的解决方案。 方法一&#xff1a;给元素加上cursor: pointer样式。&#xff08;不生效&#xff09; 方法二&#xff1a;ios事件机制不一样&#xff0c;将click事件改为mousedown或其他事件即可解决。&#xff08;需要双击&…

springboot mysql行锁_SpringBoot基于数据库实现简单的分布式锁

本文介绍SpringBoot基于数据库实现简单的分布式锁。1.简介分布式锁的方式有很多种&#xff0c;通常方案有&#xff1a;基于mysql数据库基于redis基于ZooKeeper网上的实现方式有很多&#xff0c;本文主要介绍的是如果使用mysql实现简单的分布式锁&#xff0c;加锁流程如下图&…

简单shell

执行脚本结果重定向 sh hah.sh hello 1>>/home/qiso/job.log 2>&1 上面这句话的意思是 首先通过sh执行脚本hah.sh&#xff0c;其中执行这个脚本的时候&#xff0c;需要传入参数&#xff0c;参数是hello&#xff0c; 1表示的是标准输出&#xff0c;以上脚本执行…

个人随笔、收藏——(包括技术、设计思想等)

1、开源自动化工具 Sahi、Selenium、AutoIt Sahi&#xff0c;是一个用于Web应用程序的自动测试工具。Sahi运行为一个代理服务器&#xff0c;必须把浏览器的代理设置为Sahi服务器。 然后Sahi注入javascript来访问Web页面中的元素。Sahi支持Http与Https并且独立于Web站点或Web应用…