Java 泛型 通配符 及 ? extends T 和 ? super T 的理解

本文详细介绍了Java中泛型通配符的使用,包括万能通配符?、类型上限? extends T和下限? super T的应用,以及PECS原则在生产者消费者场景中的实践。通过实例展示如何正确地在方法参数和列表操作中运用这些概念。

目录

一、通配符 (? Wildcards)

1.1 泛型上限

1.2 泛型下限

1.3 PECS原则(Producer Extends Consumer Super)


一、通配符 (? Wildcards)

1、通配符不能用于泛型定义、不能用于New泛型实例。只能用于泛型类的使用:泛型类变量声明、方法参数声明。

2、? 是万能通配符 ,表示未知类型,类型参数赋予不确定类型、任意类型

3、<? extends T> 表示类型的上限,表示参数化类型的可能是T 或是 T的子类;

4、<? super T> 表示类型上限(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object;

5、<? extends T>,<? super T> 约束的泛型类内部的类型变量

     声明泛型类变量时 使用 <? extends T> ,对泛型类内的类型变量 只能读,不能写

    声明泛型类变量时 使用 <? super T> ,对泛型类内的类型变量 可以写(只能写T或T的子类),读只能读Object

list<?> list1 = new ArrayList<Integer>();//Ok
public void test(List<?> list)//OK 
list1 =new ArrayList<?>()//Error

泛型类定义

public class Pair<T>{
   private T first;
   private T second;
   public Pair() { first = null ; second = null ; }
   public Pair(T first, T second) { this.first = first; this.second = second; }
   public T getFirst() { return first; }
   public T getSecond() { return second; }
   public void setFirst(T newValue) { first = newValue; }
   public void setSecond(T newValue) { second = newValue; }
}

public class Fruit{ }
public class Apple extends Fruit{}
public class Pear extends Fruit{}
public class RedApple extends Apple{}

1.1 泛型上限 <? extends T>

  • 对于声明了 ? extends T的变量赋值,实际类型 必须是T或是 T的子类

        List<RedApple> redapples =new ArrayList<>();

        List<Fruit> fruits=new ArrayList<>();

        List<? extends Apple> extendsAppleList=redapples ;//OK 

        List<? extends Apple> extendsAppleList=fruits;//Error

  • 声明泛型类变量时 使用 <? extends T> ,对泛型类内的约束的类型变量 只能读,不能写。

    List<? extends Apple> extendsAppleList;

    Pair<? extends Apple> extendsApplePair;


    对于extendsAppleList,约束的类型变量就是List的元素,即 不能执行add 方法
    对于extendsApplePair,约束的类型变量就是first,second两个类成员,即不能执行  setFirst,setSecond 方法
public static void testExtendsReadWriteList(List<? extends Apple> fruits){
  //可以读
  Apple fruit = fruits.get(0);

  // 能不能对List<? extends Apple>声明的变量fruits 的内部成员写:
  //fruits.add(new Apple())//Error
  //fruits.add(new Fruit()) //Error
 }
 public static void testExtendsReadWrite(Pair<? extends Apple> pair) {
  //不能对first second 两个成员变量写
  //pair.setFirst(new Fruit());//Error
  //pair.setFirst(new Apple());//Error

  //可以读
  Apple first = pair.getFirst();//ok
 }

//声明泛型类变量的赋值

List<? extends   Apple> extendsAppleList;
List<RedApple> redAppleList = new ArrayList<>();
List<Apple> appleList = new ArrayList<>();
List<Fruit> fruitList = new ArrayList<>();
       
 //ok
extendsAppleList=appleList;
//ok
extendsAppleList=redAppleList;
//Error 实际类型Fruit 不满足 ? extends  Apple
extendsAppleList= fruitList;


Pair<? extends Apple> extendsApplePair;
Pair<Fruit> fruitPair = new Pair<>();
Pair<Apple> applePair = new Pair<>();
Pair<RedApple> redApplePair = new Pair<>();

 //ok
extendsApplePair= redApplePair;
//ok
extendsApplePair=applePair;
//Error  实际类型Fruit不满足 ? extends  Apple
extendsApplePair=fruitPair;

        setFirst / add 的调用有一个类型错误,编译器只知道需要某个 Apple的子类型,但不知道
具体是什么类型。它拒绝传递任何特定的类型。毕竟 任何类型 和 ?都不匹配。
使用 getFirst /get 就不存在这个问题: 将 getFirst 的返回值赋给一个 Apple(或其父类)的引用完全合法

        原因是编译器只知道容器内是Apple或者它的派生类,但具体是什么类型不知道。可能是Apple?可能是RedApple?编译器在看到后面用Apple赋值以后,集合里并没有限定参数类型是“Apple“。而是标上一个占位符:CAP#1,来表示捕获一个Apple或Apple的子类,具体是什么类不知道,代号CAP#1。然后无论是想往里插入Apple或者RedApple或Fruit编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。

        List<? extends Apple> list不能进行add,但是,这种形式还是很有用的,虽然不能使用add方法,但是可以在初始化的时候指定不同的类型。比如:

List<RedApple> redAppleList = new ArrayList<>()

List<? extends   Apple> extendsAppleList= redAppleList;
另外,由于我们已经保证了List中保存的是Apple类或者他的某一个子类,所以,可以用get方法直接获得值
Apple apple= extendsAppleList.get(0);//读取出来的东西只能存放在Apple 或它的基类里。
Object object = extendsAppleList.get(0);//读取出来的东西只能存放在Apple 或它的基类里。
Fruit fruit= extendsAppleList.get(0);//读取出来的东西只能存放在Apple 或它的基类里。
RedAppleson = (RedApple)extendsAppleList.get(0);//只能强转

1.2 泛型下限 <? super T>

  • 对于声明了 ? super T的变量赋值,实际类型 必须是T或是 T的父类

        List<RedApple> redapples =new ArrayList<>();

        List<Fruit> fruits=new ArrayList<>();

        List<? super Apple> superAppleList=redapples ;//Error

        List<? super Apple> superAppleList=fruits;//Ok

  • 声明泛型类变量时 使用 <? super T>不影响  泛型类内的约束的类型变量的写,但值只能是 T和T的子类,不能添加T的父类。往外取只能放在Object对象里
     

    List<? superApple> superAppleList;

    Pair<? superApple> superApplePair;


    对于superAppleList,约束的类型变量就是List的元素,可以执行add 方法
    对于superApplePair,约束的类型变量就是first,second两个类成员,可以执行  setFirst,setSecond 方法
    superApplePair.setFirst(new Apple());//OK
    superApplePair.setFirst(new RedApple());//OK
    //superApplePair.setFirst(new Fruit());//Error
    Object first = pair.getFirst();//OL
//super只能添加Apple和Apple的子类,不能添加Apple的父类,读取出来的东西只能存放在Object类里
public  void  testSuperReadWrite(Pair<? super Apple> pair){
    pair.setFirst(new Apple());
    pair.setFirst(new RedApple());
    //pair.setFirst(new Fruit());//Error
    Object first = pair.getFirst();
 }
//super只能添加Apple和Apple的子类,不能添加Apple的父类,读取出来的东西只能存放在Object类里
public  void  testSuperReadWriteList(List<? super Apple> list){
    list.add(new Apple());
    list.add(new RedApple());
    //list.add(new Fruit());//Erro
    Object object = list.get(0);
}

//只能赋值是Apple及父类的的List
List<? super  Apple> superAppleList;
List<RedApple> redAppleList = new ArrayList<>();
List<Apple> appleList = new ArrayList<>();
List<Fruit> fruitList = new ArrayList<>();

//ok
superAppleList= fruitList;
//ok
superAppleList=appleList;
//Error 实际类型RedApple不满足 ? super  Apple
//superAppleList=redAppleList;

        
//只能赋值是Apple及父类的Pair
Pair<? super Apple> superApplePair;
Pair<Fruit> fruitPair = new Pair<>();
Pair<Apple> applePair = new Pair<>();
Pair<RedApple> redApplePair = new Pair<>();

//ok
superApplePair=fruitPair;
//ok
superApplePair=applePair;
//Error 实际类型RedApple不满足 ? super  Apple
superApplePair= redApplePair;
        

        因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是Apple类,那往里存粒度比Apple小的都可以。出于对类型安全的考虑,我们可以加入Apple对象或者其任何子类(如RedApple)对象,但由于编译器并不知道List的内容究竟是Apple的哪个超类,因此不允许加入特定的任何超类(如Fruit)。而当我们读取的时候,编译器在不知道是什么类型的情况下只能返回Object对象,因为Object是任何Java类的最终祖先类。但这样的话,元素的类型信息就全部丢失了

1.3 PECS原则(Producer Extends Consumer Super)

  • Producer Extends:如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;也成为Get 原则
  • Consumer Super:如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符,也称为Put原则
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值