Java泛型(通配符)

文章目录

  • 前言
    • 一、?无界通配符
    • 二、泛型不是协变的
    • 二、带有限制的通配符
      • 2.1 上限通配符
      • 2.2 下限通配符
      • 2.3 小结


前言

    在使用泛型进行编码时我们经常会遇到通配符,如T,E,K,V 等,他们之间本质上没有区别,只不过对他们约定了一些含义以便提高程序可读性,除了使用这些常用的通配符之外,我们还经常看到诸如、等带有限制的通配符。

他们所约定的含义如下所示:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
?表示不确定的 java 类型

一、?无界通配符

     例如泛型List 在没有赋值前,表示可以接受任何类型的集合赋值,赋值之后不能往里面随便添加元素,但可以remove和clear。List一般作为参数来接收外部集合,或者返回一个具体元素类型的集合。常在以下情况下使用:
  • 编写可使用 Object 类中提供的功能使用的方法时。
  • 当代码使用不依赖于类型参数的泛型类中的方法时。
public static void printList(List<?> list) {
       for (Object o : list) {
       		/* 方法体 */
        }
}

注:因为你不知道集合是哪种类型,所以只能够对集合进行读操作,并且只能把读取到的元素当成 Object 实例来对待。可参考:《Java泛型(类型擦除)》

二、泛型不是协变的

在阅读下面的内容之前我们需要了解以下知识。

——预备知识
协变:能在使用父类型的场景中改用子类型
逆变:能在使用子类型的场景中改用父类型
不变:不能做到以上两点
更加详细的解释可参考文章:《几个搞不太懂的术语:逆变、协变、不变》
数组的协变性可参考文章:《数组的协变性》

在知道上述技术术语的解释后,下面通过具有协变性质的数组进一步引出为什么泛型要设计成非协变的。

/* Person是Man和Woman的父类 */
Person[] person = new Man[4];
person[0] = new Man();
person[1] = new Woman();

由于数组是协变的,而ManWomanPerson的子类,那么Person[ ]就可以接收Man[ ]Woman[ ],换句话说就是基础类型具备父子关系,那么对应的容器类型也具备,因此这段代码将会通过编译,但是person[0]实际上是引用了Man,可是Man IS-NOT_A Woman。这样就会产生类型混乱,当代码运行时就会抛出ArrayStoreException异常。

    通过上述的案例我们了解到由于数组的协变性导致代码可以顺利编译,但也会因此在运行时抛出异常。而设计泛型的初衷就在于我们需要这种情况产生编译错误而不是运行时异常,所以泛型不是协变的。
    但设想一下泛型不是协变的而数组是协变的,那么我们将会避免使用泛型集合,因为非协变的泛型将使得代码灵活性降低,于是就设计使用带有限制的通配符来弥补这个不足。

二、带有限制的通配符

2.1 上限通配符

List 代表的是一个可以持有 A及其子类(如B和C)的实例的List集合。

public static double sum(List<? extends Number> list) {
        double sum = 0.0;
        for (Number i : list) {
            sum += i.doubleValue();
        }
        return sum;
    }
    
-------------测试---------------
List<Integer> list1 = Arrays.asList(4, 5, 6, 7);
System.out.println("Total sum is:" + GenericDemo.sum(list1));

List<Double> list2 = Arrays.asList(4.1, 5.1, 6.1);
System.out.print("Total sum is:" + GenericDemo.sum(list2));

-------------输出---------------
Total sum is:22.0
Total sum is:15.299999999999999

当您想要放宽对变量的限制时可以使用该通配符,例如要编写一个适用于 List List List 的方法,则可以使用上限通配符执行此操作。

对于上限通配符需要注意的一点就是使用上限通配符只能从结构中获取值而不能将值放入结构中。例如
(同样假设Person类是ManWoman的父类)对于下面的方法,当我们将ListListList类型的变量传入方法中时是完全合法的,此时从集合里读出元素并把它强制转换为Person是安全的,但是我们不能给传入的list插入任何元素,因为你不知道list中是什么元素(如不能给List中插入Woman)。

public void show(List<? extends Person> list){
   for(Person a : list){
      System.out.println(a.sex());
   }
}

2.2 下限通配符

public static void printOnlyIntegerClassOrSuperClass(List<? super Integer> list) {
        System.out.println(list);
    }
    
-------------测试---------------
List<Integer> list1 = Arrays.asList(4, 5, 6, 7);
GenericDemo.printOnlyIntegerClassOrSuperClass(list1);

-------------输出---------------
[4, 5, 6, 7]

对于下限通配符同样需要注意的一点就是使用下限通配符只能将值放入结构中或者将读取结果转换为Object。同样观察下面的例子我知道list中要么是Person要么是Person的父类,而ManWoman继承与Person那么Person的父类也就是ManWoman的父类,因此我们可以向其中添加Person以及Person的子类(ManWoman)是合法的。

public void show(List<? super Person> list){
   list.add(new Person());
   list.add(new Man());
   list.add(new Woman());
}

2.3 小结

在使用上述通配符时:

  1. 如果要从结构中获取值,使用上限通配符;
  2. 在将值放入结构中时,使用下限通配符。
  3. 可以为通配符指定上限,也可以指定下限,但不能同时指定两者。

对于泛型存在限制的原因可参考《Java泛型(类型擦除)》

end

你可能感兴趣的:(Java开发,java,开发语言,后端)