? 通配符类型

- <? 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

Java中? extends T和? super T的理解相关推荐

  1. java泛型? extends Fruit 和? super Fruit的理解和区别

    假设有3个类,他们的关系如下: class Fruit {}class Apple extends Fruit {}class Jonathan extends Apple{} 1.<? ext ...

  2. java中extends用法_JAVA的extends用法

    理解继承是理解面向对象程序设计的关键.在Java中,通过关键字extends继承一个已有的类,被继承的类称为父类(超类,基类),新的类称为子类(派生类).在Java中不允许多继承. (1)继承 cla ...

  3. java中this.getclass()_详解java中this.getClass()和super.getClass()的实例

    详解java中this.getClass()和super.getClass()的实例 前言: 遇到this.getClass()和super.getClass()的返回值感到疑惑,经过探索豁然开朗. ...

  4. java中getclass_详解java中this.getClass()和super.getClass()的实例

    详解java中this.getClass()和super.getClass()的实例 前言: 遇到this.getClass()和super.getClass()的返回值感到疑惑,经过探索豁然开朗. ...

  5. JAVA中extends 与implements区别

    JAVA中extends 与implements有啥区别? 1. 在类的声明中,通过关键字extends来创建一个类的子类.一个类通过关键字implements声明自己使用一个或者多个接口. exte ...

  6. java中extends与implements的区别

    JAVA中extends 与implements有啥区别?  1. 在类的声明中,通过关键字extends来创建一个类的子类.一个类通过关键字implements声明自己使用一个或者多个接口.    ...

  7. JAVA中extends与implements详解

    JAVA中extends与implements有啥区别? 1. 在类的声明中,通过关键字extends来创建一个类的子类.一个类通过关键字implements声明自己使用一个或者多个接口. exten ...

  8. Java泛型中extends T和super T的区别?

    <? extends T>和<? super T>是Java泛型中的"通配符(Wildcards)"和"边界(Bounds)"的概念. ...

  9. Java 泛型 extends T 和 super T 详解

    大概来讲: 是"上界通配符" 是"下界通配符" #####1. 为什么要用通配符和边界? 使用泛型的过程中,经常出现一种很别扭的情况.比如按照题主的例子,我们有 ...

最新文章

  1. Comware、VRP、IOS这些操作系统平台你分清了吗?
  2. 熵(Entropy),交叉熵(Cross-Entropy),KL-松散度(KL Divergence),似然(Likelihood)
  3. .Net AppDomain详解(一)
  4. 组织管理插件-代码自动补全-语法检查等特性-SpaceVim v1.5.0
  5. JAVA中日期格式SimpleDateFormat
  6. 2018智慧零售白皮书
  7. [BJWC2011]元素
  8. leetcode 374. Guess Number Higher or Lower 、375. Guess Number Higher or Lower II
  9. Loadrunner 第一个场景设计Controller
  10. wps 字体对系统无效_wps 字体不显示怎么办 - 卡饭网
  11. 极兔速递电子面单API接口-快递鸟
  12. 小程序第三方平台初体验(上)|微专辑
  13. linux文件句柄上线的修改,修改Linux文件句柄限制
  14. python documents in chinese_基于 Python 的简单自然语言处理实践
  15. 修改服务器bond网口mode4,双25GE网卡做bond4测试,其中一个网口没有流量一个网口可以打满的问题分享★★★...
  16. 前端(内部)-编程规约
  17. 巨富笔记:上者劳人,中者劳智,下者劳力
  18. matlab simulink的scope 示波器光标如何移动记录
  19. python数据去重_python 大数据去重
  20. 《登黄山偶感》艺术品赏

热门文章

  1. shell学习之路:流程控制(if)
  2. exchange 2010 search mailbox 的幕后强大功能
  3. Windows内存管理和linux内存管理
  4. Spring+Hibernate项目在weblogic中部署的一些问题
  5. 如何解决资料下载下来为index.html和PHP文件的问题?
  6. cisco路由交换系统测试命令
  7. 如何充分利用JavaScript(ES6)中的解构功能
  8. Wireshark分析实战:某达速递登录帐号密码提取
  9. ESXi6.5环境搭建(五:常见问题及解决方案实验总结)
  10. unix odbc php 连接sqlserver,Ubuntu下通过unixODBC连接MS SqlServer2005