zoukankan      html  css  js  c++  java
  • 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> 表示 “具有任何从Father继承类型的列表”,编译器无法确定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
    
    ", 
                       3, 4, 5, maximum( 3, 4, 5 ) );
    
           System.out.printf( "Maxm of %.1f,%.1f and %.1f is %.1f
    
    ",
                       6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );
    
           System.out.printf( "Max of %s, %s and %s is %s
    ","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、无法创建泛型数组。


    4、泛型方法的使用

      • static <E> List<E> asList(E[] a) —- 泛型方法 
        参考这篇文章Java中的泛型方法的解释,注意区分Class与实例。
  • 相关阅读:
    部署 AppGlobalResources 到 SharePoint 2010
    还原一个已删除的网站集
    使用仪表板设计器配置级联筛选器 (SharePoint Server 2010 SP1)
    File or arguments not valid for site template
    Pex and Moles Documentation
    Content Query Webpart匿名访问
    Running Moles using NUnit Console from Visual Studio
    Calling a WCF Service using jQuery in SharePoint the correct way
    Updating Content Types and Site Columns That Were Deployed as a Feature
    asp.net中判断传过来的字符串不为空的代码
  • 原文地址:https://www.cnblogs.com/chenxibobo/p/9655236.html
Copyright © 2011-2022 走看看