天天看点

Java范型:通配符中super、extends的区别一、概要二、构建类树三、代码解读

一、概要

java范围有三种形式:

  1.  <? extends T>: 上界通配符, ?表示继承自T的类(沿着类图,上边界是T)。频繁往外读取内容,适合采用上界通配符。
  2.  <? super T>:下界通配符,?表示T及其父类(沿着类图,下边界是T)。频繁插入内容,适合采用下界通配符。
  3. <?>:某个类型。单纯表示引用某一类型,不进行插入或读取
  4. 如果频繁读取或插入,尽量避免使用通配符,以免数据丢失。

二、构建类树

为了清晰表明概要中的意识,先构建类树,用于概念细化、代码解读。此处解读参考Think In Java书的案例。

class Fruit {
    public void getClassName() {System.out.println(this.getClass().getSimpleName());}
}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Maigold extends Apple {}
class Orange extends Fruit { }
class Mandarin extends Orange {}
class Tangerine extends Orange {}
           
Java范型:通配符中super、extends的区别一、概要二、构建类树三、代码解读

以上即为本次案例所使用的类图及继承关系

Java范型:通配符中super、extends的区别一、概要二、构建类树三、代码解读

如上图,

  • <? extends Apple>为上界通配符,即上界是Apple,?表示继承自Apple的类,作用范围是红色三角内部的类
  • <? super Apple>为下界通配符,即下界是Apple,?表示Apple及其父类,作用范围是蓝色三角内部的类。

三、代码解读

3.1 泛型容器无法进行向上转型

import java.util.ArrayList;
import java.util.List;

public class NonCovariantGenerics {
    // 编译错误,无法进行向上转型
    List<Fruit> flist = new ArrayList<Apple>();
}
           

上述代码会引发编译错误,无法将List<Apple>向上转型为List<Fruit>,即无法将“涉及Apple的范型赋给涉及Fruit的范型”。当然这违背生活常识,Apple竟然无法放到Fruit里。但从代码角度思考,Apple的List不是Fruit的List,因为List<Apple>将持有Apple及其子类,而List<Fruit>能够持有Fruit子类,范围远大于List<Apple>。

如果想在泛型容器间建立转型关系,使代码描述更加人性化,即面向对象,则需要采取通配符。

3.2 上界通配符 <? extends T>

import java.util.ArrayList;
import java.util.List;

public class GenericsAndCovariance {
    public static void main(String[] args) {
        // 上界通配符进行容器的向上转型
        List<? extends Fruit> flist = new ArrayList<Apple>();
        // 以下均会引起编译错误
        // flist.add(new Apple());
        // flist.add(new Fruit());
        // flist.add(new Object());
        flist.add(null); // Legal but uninteresting
        // We know that it returns at least Fruit:
        Fruit f = flist.get(0);
        f.getClassName();   //List里只有空指针,无法调用该函数
    }
} 
           

通过上述代码例子得知,List<? extends Fruit>并不是说该List可以持有任何类型的Fruit,通配符引用的是明确的类型。因此,该处可以这样解译“List<? extends Fruit> 持有某个具体类型,该类型具体是什么我不清楚,但我可以具定它是Fruit的子类型”。

可见,因为无法确定List<? extends Fruit>具体持有什么类型,故向内部填加对象比较危险,故调用add(Object object)会引发失入。但是可以使用get(int index)方法。

可见,上界通配符主要支持读取操作,应尽量避免写入操作,以免引发异常。

3.3 绕过编译器

import java.util.*;

public class CompilerIntelligence {
    public static void main(String[] args) {
        List<? extends Fruit> flist =
                Arrays.asList(new Apple(), new Fruit(), new Orange(), new Jonathan());

        Apple apple = (Apple)flist.get(0); // 利用get获取类
        apple.getClassName();

        for (int i = 1; i < flist.size(); i++) {
            Fruit temp = flist.get(i); //利用动态编译,指向不同的类
            temp.getClassName();
        }

        flist.contains(new Apple()); // 调用boolean contains(Object o);
        flist.indexOf(new Apple()); // 调用int indexOf(Object o);
        //flist.add(new Apple()); //调用boolean add(E e);失败
    }
}
/**
 * OUTPUT
 * Class name is : Apple
 * Class name is : Fruit
 * Class name is : Orange
 * Class name is : Jonathan
 */
           

上述代码可知:

  • 借助Arrays.asList将Fruit及其子类,以List的形式存储到List<? extends Fruit>,变相实现了add,只是有些机械化。
  • 利用强制转型,获取对应的Apple类型
  • 利用动态编译,动态引用Orange, Jonathan类型
  • contains 和indexOf方法的参数是Object,不涉及通配符,所以可以成功执行
  • add的方法参数是泛型,在本案例中即是<? extends Fruit>,是通配符,无法明确具体类型,所以编译失败

3.4 下界通配符<? super T>

下界通配符,直白些,可以理解为支持向下转型,其作用域可以参见本篇第二章图的蓝色区域

import java.util.*;

public class SuperTypeWildcards {
    public static void main(String[] args ) {
        //支持向下转型,不支持向上转型
        List<? super Apple> appleList = new ArrayList<Fruit>();
        //List<? super Apple> appleListTwo = new ArrayList<Jonathan>();转型失败
        //appleList = new ArrayList<Jonathan>();转型失败,无法进行
        appleList.add(new Apple());
        //appleList.add(new Fruit());编译失败,List<Fruit>可以向下转型为List<? super Apple>,
        // 但是转型后,就只能放Apple相关类了
        appleList.add(new Jonathan());
        appleList.add(new Maigold());
        //appleList.add(new Orange());编译失败,Orange不是Apple
        for (int i = 0; i < appleList.size(); i++) {
            Apple temp = (Apple) appleList.get(i);
            temp.getClassName();
        }
    }
}
/**OUTPUT
 * Class name is : Apple
 * Class name is : Jonathan
 * Class name is : Maigold
 */
           

上述代码得知

  • <? super T>支持向下转型,不支持向上转型,作用范围参见本篇图2。我把这理解为下界通配符的通俗含义
  • <? super T>支持插入数据类,插入的数据必须是Apple及其子类
  • <? super T>通过强制类型转换,也可以取数据,可能会存在数据丢失情况

3.5 无界通配符 <?>

用一句歌词来表达无界通配符,“我不知道你是谁,但我知道你为了谁”。无界通配符作用等价于使用原生类型,其表明使用泛型进行类型引用,在不清楚或者无需清楚具体类型的前提下。