关于java泛型中List<? extends Base>的疑问?

现在有代码如下,Sub是Base的子类:

public class Base {

}

public class Sub extends Base{

}

我能理解下面的代码:

    // 相当于list2的泛型是Base或者Base的超类,

// 所以,list2中可以放Base或者Base的子类,因为list2的泛型如果是Base,那显然可以添加Base的实例;

// Base的泛型如果是其超类,那显然,Base的任意子类都能向上转型为Base的超类

List<? super Base> list1 = new ArrayList<>();

list1.add(new Sub());

list1.add(new Base());

现在的问题是,为什么不能调用list2的add方法添加,即便是添加Base实例也不行?

List<? extends Base> list2 = new ArrayList<>();

如果按照上面的理解,list2的泛型可以是Base或者Base的子类,如果说子类之间不能互相转型,那么,至少可以添加Base的实例吧?或者说,添加Base的子类也能转为Base类型,为什么java不允许这么做呢?


回答:

因为 List<Base> 表示 里面是 Base 类型的对象
但是 List<? extends Base> 表示 这是一个 List<Base> 或者 List<Sub>
因此无法判断到底能放进去什么

这里有一个比较全的例子

class Scratch {

public static void main(String[] args) {

// 类型参数是一个确定的类型的时候,读写操作都可以执行

Bag<Fruit> fruitBag = new Bag<>();

fruitBag.set(new Apple());

fruitBag.set(new Pear());

Fruit fruit = fruitBag.get();

Bag<Apple> appleBag = new Bag<>();

Bag<Pear> pearBag = new Bag<>();

// 这里会报错,因为 Bag<Fruit> 和 Bat<Apple> Bag<Pear> 类型不兼容

Bag<Fruit> badBag1 = appleBag;

Bag<Fruit> badBag2 = pearBag;

// 这里可以通过,因为 Bag<? extend Fruit> 和 Bag<Apple> 和 Bag<Pear> 类型兼容

Bag<? extends Fruit> appleFruitBag = appleBag;

Bag<? extends Fruit> pearFruitBag = pearBag;

// extends 的情况,只能'读'不能'写'

// 因为 Bag<? extend Fruit> 可能是 Bag<Apple> 也可能是<Pear>

// 所以无法确定能塞进去什么

appleFruitBag.set(new Apple());

appleFruitBag.set(new Pear());

pearFruitBag.set(new Apple());

pearFruitBag.set(new Pear());

// 但是无论是什么,都是 Fruit,所以可以读取出来

Fruit fruit1 = appleFruitBag.get();

Fruit fruit2 = pearFruitBag.get();

// super 的情况,只能'写'不能'读'(只能读出 Object)

Bag<Object> objectBag = new Bag<>();

Bag<? super Apple> superAppleBag1 = fruitBag;

Bag<? super Apple> superAppleBag2 = objectBag;

Bag<? super Apple> superAppleBag3 = appleBag;

// 因为类型范围都比 Apple 大,所以可以放 Apple 进去

superAppleBag1.set(new Apple());

superAppleBag2.set(new Apple());

// 因为不知道具体的类型是什么,因此也不能放父类对象进去

superAppleBag2.set(new Fruit());

// 同样因为不知道具体类型是什么,只能读 Object 类型出来

Object o = superAppleBag2.get();

}

public static class Bag<T> {

public void set(T t) {

}

public T get() {

return null;

}

}

public static class Fruit {

}

public static class Apple extends Fruit {

}

public static class Pear extends Fruit {

}

}


回答:

题主理解的是错误的:

  • extends 可用于返回类型限定,不能用于参数类型限定(换句话说:? extends xxx 只能用于方法返回类型限定,jdk能够确定此类的最小继承边界为xxx,只要是这个类的父类都能接收,但是传入参数无法确定具体类型,只能接受null的传入)。
  • super 可用于参数类型限定,不能用于返回类型限定(换句话说:? supper xxx 只能用于方法传参,因为jdk能够确定传入为xxx的子类,返回只能用Object类接收)。

详细:

https://zhuanlan.zhihu.com/p/267335337


回答:

extends 和 super 这两个关键词使用场景不一样:

  • 对于方法传递参数时: 对于一个集合类型,super T 对应放入生产场景(add T 的子类), extends 对应取出消费场景( 调用 T 的方法) ,

举个例子:

import java.util.ArrayList;

import java.util.List;

public class GenericTest {

static class GrandParent{

public void say(){

System.out.println("GrandParent");

}

}

static class Parent extends GrandParent{

public void say(){

System.out.println("Parent");

}

public void otherSay(){

System.out.println("other from Parent");

}

}

static class Child extends Parent{

public void say(){

System.out.println("Child");

}

public void otherSay(){

System.out.println("other from Child");

}

public void childSay(){

System.out.println("childSay");

}

}

public static void say(List<? extends Parent> p){

for(GrandParent g:p){

g.say();

}

for(Parent g:p){

g.otherSay();

}

// for(Child g:p){ // compile error

// g.childSay();

// }

// p.add(new Child()); // 消费者不能添加元素

}

public static void add(List<? super Parent> p){

p.add(new Child());

p.add(new Parent());

// p.add(new GrandParent()); // compile error

// Parent c = p.get(0); // 生产者不能获取元素

}

public static void main(String[] args) {

List<Parent> family = new ArrayList<>(); // 生产者同时也是消费者,则只能使用明确的类型

add(family);

say(family);

List<Child> family2 = new ArrayList<>();

// add(family2); // compile error

say(family2);

List<GrandParent> family3 = new ArrayList<>();

add(family3);

// say(family3); // compile error

List<? extends Parent > family4 = new ArrayList<>();

// add(family4); // compile error

say(family4);

List<? super Parent> family5 = new ArrayList<>();

add(family5);

// say(family5); // compile error

}

}

看这里: https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java


回答:

泛型可以理解为一种编译约束,能够在编译阶段找出来所有违反约束的问题,它是声明式的
约束就会有很多预先定义好的规则,你这里遇到的extends和super的问题,是泛型里面的PECS规则限制的
PECS = Producer Extends,Consumer Super
当定义泛型容器Collection<?> 的时候,
如果希望容器是个生产者,使用extends,生产型容器的约束是:我能够提供汽车轮胎(Tier),但可能来自任意品牌,你可以读取我,获取轮胎,自行判断是不是你需要的品牌,但是我不接收轮胎,我的职责是单一的,只读不存
如果希望容器是个消费者,使用Super,消费型容器的约束是:我能够存放轮胎,你可以添加任意品牌的轮胎,但是我不提供轮胎,我的单一职责,只存不读

这个规则的本质是:面对对象是支持型变的(子类 is a 父类),但泛型作为一种对象类型的安全性约束,本身是不支持型变的
所以如果单纯定义List<Parent>和List<Child>,两者是没有任何继承关系的,但有的时候我们又希望能够约束容器中元素的型变范围,所以大神们就发明使用了extends/super,这里的有的时候指的就是生产或者消费场景。
再进一步,抛开容器泛型的使用场景和PECS,泛型的extends/super本身涉及到概念叫协变(Covariance)和逆变(Contravariance),挺难一下子解释清楚的两个概念,可以多搜一些资料,了解一下Fruit和Apple的故事...

总结一下,遇到使用容器泛型的场景,又希望约束下容器支持的行为,那只读不存用extend,只存不读用super,不约束就只用问号,这个一般通常配合函数来使用,约束函数的使用场景,提供安全性。
举例:Collections提供的copy方法,src只读不存,dest只存不读

    public static <T> void copy(List<? super T> dest, List<? extends T> src) {

int srcSize = src.size();

if (srcSize > dest.size())

throw new IndexOutOfBoundsException("Source does not fit in dest");

if (srcSize < COPY_THRESHOLD ||

(src instanceof RandomAccess && dest instanceof RandomAccess)) {

for (int i=0; i<srcSize; i++)

dest.set(i, src.get(i));

} else {

ListIterator<? super T> di=dest.listIterator();

ListIterator<? extends T> si=src.listIterator();

for (int i=0; i<srcSize; i++) {

di.next();

di.set(si.next());

}

}

}

以上是 关于java泛型中List&lt;? extends Base&gt;的疑问? 的全部内容, 来源链接: utcz.com/p/945218.html

回到顶部