关于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<? extends Base>的疑问? 的全部内容, 来源链接: utcz.com/p/945218.html