Java随记-Java泛型中的通配符

前言

泛型中的类型参数变量T、K等经常在代码中使用

?即泛型中使用的通配符,在阅读源码中常会出现如Collection c这样的表示,这样表示什么意思呢?为什么有时候插入数据的时候会报错呢?


类型参数变量和通配符的区别

此时想输出苹果集合,如果采取以下写法无法满足要求:

// 打印集合
public static void print(List<Fruit> list) {
    for (Fruit fruit : list) {
        System.out.println(fruit);
    }
}
List<Apple> appleList = new ArrayList<>();
Apple apple1 = new Apple();
Apple apple2 = new Apple();
appleList.add(apple1);
appleList.add(apple2);
print(appleList);  //报错,因为此时的类型参数变量T=Fruit,所以list只能装有Fruit类型的元素

可以使用以下两个方法解决上述问题:

  • 使用泛型Textends
public static <T extends Fruit> void print(List<T> list) {
    for (T fruit : list) {
        System.out.println(fruit);
    }
}
  • 使用通配符
public static void print(List<? extends Fruit> list) {
    for (Fruit fruit : list) {
        System.out.println(fruit);
    }
}

上述例子展现出了通配符?和类型参数变量T是存在一定区别的:

  • 两者使用extends的位置不同
  • 会报错,而不会

泛型通配符的使用和作用

泛型通配符主要解决以下两个需求(对于使用了上界或下界的通配符的读取和插入是有限定的,后续会具体说明):

  • 从一个泛型集合里面读取元素

  • 往一个泛型集合里面插入元素

要定义一个使用了泛型的集合,可以有以下三种方式:

List<?> genericsUnbounded = new ArrayList<>();
List<? extends A> genericsUpperbounded = new ArrayList<>();
List<? super A> genericsLowerbounded = new ArrayList<>();

Tips:


无限定通配符

无限定即没有加上extendssuper关键字,比如List genericsUnbounded ,它的意思是这个集合是一个可以持有任意类型的集合

  • 由于可以是任意类型的集合,导致不知道集合具体是哪种类型,所以只能够对集合进行读操作
List<?> genericsUnbounded = new ArrayList<>();
genericsUnbounded.add("psj");  // 报错
genericsUnbounded.get(0);  // ok
  • 当在函数的形参中使用无限定通配符时,除了在函数内部不能进行写操作,读取的时候应该直接使用Object类型接收元素:
// read函数
public void read(List<?> elements){
    for(Object o : elements){
        System.out.println(o);
    }
}
// 传入任何类型的集合都可
List<A> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
read(list1);
read(list2);

上界通配符

上界即加上extends关键字,比如List genericsUpperbounded代表了一个可以持有A及其子类实例的List集合

  • 尽管确定了集合中的元素是AA的子类,但依旧只能够对集合进行读操作,因为无法确定插入元素的类型
List<? extends A> genericsUpperbounded = new ArrayList<>();
B a = new B();
// 假设原本想让genericsUpperbounded全部存储C类元素,此时想添加一个B类元素肯定不符合自己的要求,干脆直接报错
genericsUpperbounded.add(a);  // 报错
genericsUnbounded.get(0);  // ok
  • 当在函数的形参中使用上界通配符时,除了在函数内部不能进行写操作,读取的时候可以使用AObject类型接收元素:
// read函数
public void read(List<? extends A> elements){
    for(A a : elements){
        System.out.println(a);
    }
}
// 只能传入A或A的子类元素集合
List<A> list1 = new ArrayList<>();
List<B> list2 = new ArrayList<>();
List<String> list3 = new ArrayList<>();
read(list1);
read(list2);
read(list3);  // 报错

下界通配符

下界即加上super关键字,比如List genericsLowerbounded代表了一个可以持有A及其父类实例的List集合

  • 因为确定了集合中能包含的元素是AA的父类,所以可向集合中插入A和A的子类元素
List<? super A> genericsLowerbounded = new ArrayList<>();
genericsLowerbounded.add(new A());
// 假设原本想让genericsLowerbounded全部存储A类元素,此时想添加一个B类元素,根据Java的多态是可以添加的进集合的
genericsLowerbounded.add(new B());
  • 当在函数的形参中使用下界通配符时,在函数内部进行取操作时得到的元素必须为Object类型,因为无法确定是A类还是其父类元素:
// insert函数
public void insertElements(List<? super A> list) {
    list.add(new A());
    list.add(new B());
    A a = list.get(0);  // 报错
}
// 只能传入A或A的父类元素集合
List<A> list1 = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
List<B> list3 = new ArrayList<>();
insert(listA);
insert(listObject);
insert(list3);  // 报错

参考

Java泛型中的通配符

一文读懂Java泛型中的通配符 ?


你可能感兴趣的:(Java,java,泛型)