`

Java中<? extends T>和<? super T>的理解(转)

 
阅读更多
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与实例。
分享到:
评论

相关推荐

    Java中List<? extends T>与List<? super T>的区别(值得珍藏)

    在Java中,List&lt;T&gt;、List&lt;? extends T&gt;和List&lt;? super T&gt;是泛型的不同使用形式,它们在类型约束和操作上有所不同。 1. List&lt;? extends T&gt; - `? extends T` 是类型上界的表示,意味着列表中的元素可以是T类型或者是...

    简单粗暴一文彻底搞懂Java泛型中的PECS原则(在坑里躺了多年终于爬出来了).md

    Java泛型中的PECS原则是程序员在处理带有泛型的集合时需要遵循的一条编程指导原则,它是英文单词Producer-Extends, Consumer-Super的缩写。PECS原则来自于《Effective Java》一书中提到的一条原则,即“Get and put ...

    collections方法集合

    List&lt;String&gt; flavors = new ArrayList&lt;&gt;(); Collections.addAll(flavors, "Peaches'nPlutonium", "RockyRacoon"); ``` 此例中,`flavors`列表将被填充上指定的字符串元素。 #### 2. `asLifoQueue` 此方法将`...

    java中泛型上下界问题基本问题的介绍

    extends Fruit&gt;`可以视为`Plate&lt;Fruit&gt;`及`Plate&lt;Apple&gt;`等所有`Fruit`的子类的容器的共同基类。这样就可以将`Plate&lt;Apple&gt;`实例赋值给`Plate&lt;? extends Fruit&gt;`类型的变量: ```java Plate&lt;? extends Fruit&gt; p = ...

    使用通配符简化泛型使用1

    extends T&gt; 和 &lt;? super T&gt;)。 3. 无界通配符(&lt;?&gt;): 无界通配符表示没有任何特定类型的约束,意味着它可以代表任何类型。例如,`List&lt;?&gt;`表示一个可以存储任何类型元素的列表,但不允许添加元素,只能读取。 ...

    集合工具类Collections的基本应用

    - `sort(List&lt;T&gt; list)`: 这个方法用于对List接口的实现类对象进行原地排序,要求List中的元素必须实现Comparable接口或者在调用时传入自定义的Comparator。例如,对整型ArrayList进行升序排序可以使用`Collections...

    Java泛型三篇文章,让你彻底理解泛型(super ,extend等区别)

    在 Java 泛型中,extends 和 super 是两个重要的关键字,它们分别用于限定类型的上界和下界。 extends 用于限定类型的上界,表示参数化类型可能是 T 或 T 的子类。例如: List&lt;? extends Fruit&gt; flist = new ...

    559.557.JAVA基础教程_集合-Collections工具类常用方法的测试(559).rar

    Java集合框架是Java编程中不可或缺的部分,而Collections工具类则是这个框架中的一个重要工具,它提供了大量静态方法,用于操作各种集合接口(如List、Set、Queue等)的实例。本教程将深入探讨Collections工具类中的...

    Collections Framework中的算法(之一)――综述

    extends T&gt; subList)` 和 `Collections.lastIndexOfSubList(List&lt;T&gt; list, List&lt;? extends T&gt; subList)` 用于查找子列表第一次或最后一次出现的位置。 - **二分查找**:`Collections.binarySearch(List&lt;? extends ...

    K_Generic--Bounds Wildcards知乎回答1

    在Java泛型中,通配符(Wildcards)和边界(Bounds)是为了解决类型兼容性和灵活性的问题。通配符允许我们定义泛型类型的限制,而边界则指定了这些限制的具体范围。 1. 为什么要使用通配符和边界? 在Java中,泛型...

    Java泛型总结(2)进阶篇

    public &lt;T extends Comparable&lt;T&gt;&gt; void sort(List&lt;T&gt; list) { // sorting logic } ``` 这里的 `&lt;T extends Comparable&lt;T&gt;&gt;` 就是一个带有边界约束的类型参数。 3. 类型擦除 Java泛型在编译后会进行类型擦除...

    关于java基础的泛型的练习

    - 泛型类的实例化必须提供实际类型参数,如`MyList&lt;String&gt; list = new MyList&lt;&gt;();` - 对于无参构造的泛型类,可以使用匿名内部类的方式省略尖括号,如`new MyList&lt;String&gt;() {}`。 9. 泛型和静态方法: - 泛型...

    Collections源码java-Java-Collection-:对Java的Collection框架源码阅读

    super T&gt; list, T obj)`将指定的元素填充到整个列表中,`singleton(E e)`和`singletonList(E e)`则分别创建只包含一个元素的Set和List。 6. **集合查找和替换**:`binarySearch(List&lt;? extends Comparable&lt;? super...

    Optional 使用方法详解

    super T&gt; consumer)`:如果`Optional`中有值,就将值传递给Consumer接口的accept方法,不会产生任何返回值。 - `filter(Predicate&lt;? super T&gt; predicate)`:根据提供的谓词对值进行过滤,如果值存在且满足条件,...

    泛型&通配符常见面试题总结

    super T&gt; dest, List&lt;T&gt; src)`,这个方法可以将src列表的元素复制到dest,只要dest能接受T类型或其超类。 3. 无界通配符:`?` 表示任意类型,但不能创建实例,只能作为引用类型使用。 面试题示例: 1. 为什么不...

    java基础学习笔记之泛型

    泛型是Java编程语言中的一个重要特性,首次在JDK 1.5版本中引入,它允许程序员在定义类、接口和方法时使用类型参数,从而实现了类型的安全性和代码的复用性。泛型的主要目标是增强类型安全,避免在运行时出现...

    Java Generics and Collections

    extends T&gt;`和`&lt;? super T&gt;`允许你编写更通用的代码,提高代码重用性。 1. **无界通配符**:`&lt;?&gt;`表示任意类型,可以用于读取但不能修改集合元素。 2. **上限通配符**:`&lt;? extends T&gt;`表示类型T或其子类,常用于...

    Java泛型_Java中的泛型结构_

    Java泛型是Java编程语言中一个强大的特性,它允许在定义类、接口和方法时使用类型参数,从而实现参数化类型。泛型的主要目标是提高代码的类型安全性和重用性,减少类型转换的麻烦,并在编译时捕获可能的类型错误。...

Global site tag (gtag.js) - Google Analytics