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

目录

一、通配符 (? 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;

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

1.1 泛型上限

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{}

public static void testExtendsReadWriteList(List<? extends Apple> fruits){
  //以下都属于 Consumer 
  Apple fruit = fruits.get(0);
  //fruits.add(new Apple())//Error
  //fruits.add(new Fruit()) //Error
 }
 public static void testExtendsReadWrite(Pair<? extends Apple> pair) {
  //以下都属于 Consumer 
  //pair.setFirst(new Fruit());//Error
  //pair.setFirst(new Apple());//Error
  pair.setSecond(null);
  Apple first = pair.getFirst();//ok
 }

//OK
public List<? extends Apple> testExtendsProducerList(){
   List<Apple> apples =new ArrayList<>();
   apples.add(new Apple());
    return apples;
}

// Producer 端测试
//ok 
 List<Apple> apples =new ArrayList<>();
 apples.add(new Apple());
 testExtendsReadWriteList(apples);

//ok
List<RedApple> redapples =new ArrayList<>();
redapples.add(new RedApple());
testExtendsReadWriteList(redapples);

//ok
List<? extends Apple> apples1 = testExtendsProducerList();

//Error 不满足 函数 ? extends Apple
List<? extends Fruit> fruits= new ArrayList<>();
testExtendsReadWriteList(fruits);//Error

List<? extends Apple> apples2= new ArrayList<>();//ok
testExtendsReadWriteList(apples2);//ok
//apples2.add(new Apple())//error 这本身是一个Consumer

//ok
Pair<Apple> fruitPair1 = new Pair<>();
testExtendsReadWrite(fruitPair1);

//ok
Pair<RedApple> redApplePair = new Pair<>();
testExtendsReadWrite(redApplePair);

//Error 不满足 函数 ? extends Apple
Pair<? extends  Fruit> fruitPair = new Pair<>();
testExtendsReadWrite(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<? extends Apple> list1 = getAppleList();//getAppleList方法会返回一个Apple的子类的list
另外,由于我们已经保证了List中保存的是Apple类或者他的某一个子类,所以,可以用get方法直接获得值
List<? extends Apple> list1 = new ArrayList<>();
Apple apple= list1.get(0);//读取出来的东西只能存放在Apple 或它的基类里。
Object object = list1.get(0);//读取出来的东西只能存放在Apple 或它的基类里。
Fruit fruit= list1.get(0);//读取出来的东西只能存放在Apple 或它的基类里。
Son son = (Son)list1.get(0);//只能强转

  • <? extends T> 表示类型的上限,表示参数化类型的可能是T 或是 T的子类
  • 对于声明了 ? extends T的变量赋值,实际类型 必须是T或是 T的子类

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

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

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

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

  • 对于 带有 <? extends T> 约束的 ,只能读取
     

1.2 泛型下限

//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);
}

List<? super Fruit> fruits1 = new ArrayList<>();
testSuperReadWriteList(fruits1);

//Error 不满足 函数 ? super Apple
List<? super RedApple> apples1 = new ArrayList<>();
testSuperReadWriteList(apples1);

Pair<? super Fruit> fruitPair2 = new Pair<>();
testSuperReadWrite(fruitPair2);

//Error 不满足 函数 ? super Apple
Pair<RedApple> redApplePair2 = new Pair<>();
testSuperReadWrite(redApplePair2);

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

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

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

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

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

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

  • 下限<? super T>不影响往里存,但值只能是 T和T的子类,不能添加T的父类。但往外取只能放在Object对象里

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、付费专栏及课程。

余额充值