java泛型通配符详解

458 阅读3分钟

泛型中常用的通配符

泛型中常用的通配符有T,E,K,V,?。本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:

  • ?表示不确定的 java 类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element

?无界通配符

先从一个小例子看起,原文在这里。

我有一个父类 Animal 和几个子类,如狗、猫等,现在我需要一个动物的列表,我的第一个想法是像这样的:

List<Animal> listAnimals

但是老板的想法确实这样的:

List<? ``extends Animal> listAnimals

为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。

static int countLegs (List<? extends Animal > animals ) {
    int retVal = 0;
    for ( Animal animal : animals )
    {
        retVal += animal.countLegs();
    }
    return retVal;
}

static int countLegs1 (List< Animal > animals ){
    int retVal = 0;
    for ( Animal animal : animals )
    {
        retVal += animal.countLegs();
    }
    return retVal;
}

public static void main(String[] args) {
    List<Dog> dogs = new ArrayList<>();
     // 不会报错
    countLegs( dogs );
    // 报错
    countLegs1(dogs);
}

当调用 countLegs1 时,就会飘红。

所以,对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即),表示可以持有任何类型。像 countLegs 方法中,限定了上届,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错。而 countLegs1 就不行。

上界通配符 < ? extends E>

上届:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功
  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用

类型参数列表中如果有多个类型参数上限,用逗号分开

private <K extends A, E extends B> E test(K arg1, E arg2){
    E result = arg2;
    arg2.compareTo(arg1);
    //.....
    return result;
}

上界通配符主要用于读数据

public class test2 {
    /**
     * 1.测试泛型的上界
     */
    public static void main1(String[] args) {
        //1.实例化一个盘子对象
        Plate<Apple> applePlate = new Plate<>();
        //2.调用盘子类中的方法
        applePlate.setMessage(new Apple());
        Plate<Banana> bananaPlate = new Plate<>();
        bananaPlate.setMessage(new Banana());
        //3.进行获取盘子的信息
        func1(applePlate);
        func1(bananaPlate);
 
    }
    public static void func1(Plate<? extends Fruit> tmp){
        //tmp:传入的参数为Fruit的子类或者本身
        Apple apple = new Apple();
        //1.此时的tmp传入的不一定是apple,所以不能将传入,传入的只能是确定是tmp子类,但是这个子类不确定,因为tmp不确定。
        //tmp.setMessage(apple);
        //tmp.setMessage(new Apple());
 
        //2.但是可以进行接收,因为tmp的上界已经确定,只需要拿上界进行接收就可以,此时有可能发生向上转型
        Fruit fruit = tmp.getMessage();
        System.out.println(fruit);
    }
}

下界通配符 < ? super E>

下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object 在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

private <T> void test(List<? super T> dst, List<T> src){
    for (T t : src) {
        dst.add(t);
    }
}

public static void main(String[] args) {
    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();
    new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子类
class Dog extends Animal {

}

dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src 。

下界通配符主要用于写数据

public class test2 {
    /**
     * 2.测试泛型的下界
     */
    public static void main(String[] args) {
        //1.实例化一个盘子对象
        Plate<Fruit> fruitPlate = new Plate<>();
        //2.调用盘子类中的方法
        fruitPlate.setMessage(new Apple());
        Plate<Food> foodPlate = new Plate<>();
        foodPlate.setMessage(new Banana());
        //3.通过泛型进行获取盘子的信息
        func2(fruitPlate);
        func2(foodPlate);
    }
    public static void func2(Plate<? super Fruit> tmp){
        // tmp:传入是Fruit的父类或者本身
 
        // 1.此时调用set方法是传入的参数可以确定为tmp的子类,因为tmp的下界已经确定,此时传入下界的子类即可
        Apple apple = new Apple();
        tmp.setMessage(apple);
 
        tmp.setMessage(new Apple());
        tmp.setMessage(new Banana());
 
        // 2.但是不可以接收,因为不确定是哪个父类或者是不是自己本身,只能直接输出。
        // Fruit fruit = tmp.getMessage();
        System.out.println(tmp.getMessage());
    }
}