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

前言

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

?即泛型中使用的通配符,在阅读源码中常会出现如Collection<? extends E> 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的位置不同
  • <T super Fruit>会报错,而<? super Fruit>不会

泛型通配符的使用和作用

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

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

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

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

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

Tips:

  • 后续提到的BA的子类
  • 不知道集合是哪种类型(List<A>List<B>属于不同类型并不是因为集合中持有的元素一个是A一个是B),则集合所持有的元素类型也就不确定,所以不能随便向集合添加元素

无限定通配符

无限定即没有加上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<? extends A> 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<? super A> 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泛型中的通配符 ?


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值