通用的下限未定界通配符 vs 上限有界通配符

3
import java.util.List;
import java.util.ArrayList;

interface Canine {}
class Dog implements Canine {}
public class Collie extends Dog {
    public static void main(String[] args){
        List<Dog> d = new ArrayList<Dog>();
        List<Collie> c = new ArrayList<Collie>();
        d.add(new Collie());
        c.add(new Collie());
        do1(d); do1(c);
        do2(d); do2(c);
    }
    static void do1(List<? extends Dog> d2){
        d2.add(new Collie());
        System.out.print(d2.size());
    }
    static void do2(List<? super Collie> c2){
        c2.add(new Collie());
        System.out.print(c2.size());
    }
}

该问题的答案表明,当一个方法接受一个通配符泛型类型时,这个集合可以被访问或修改,但不能同时进行。 (Kathy和Bert)
“当一个方法接受一个通配符泛型类型时,这个集合可以被访问或修改,但不能同时进行”是什么意思?
据我所知, 方法do1有List<? extends Dog> d2,因此只能访问d2而不能修改它。 方法d2有List<? super Collie> c2,因此可以访问和修改c2,且没有编译错误。 泛型指南

请参阅Java泛型中'super'和'extends'的区别:[https://dev59.com/Q3I-5IYBdhLWcg3wVWti] - Jesper
请查看Java泛型:什么是PECS? - Jesper
请参阅《通配符在泛型中的应用:为什么“? super T”可行而“? extends T”不行?》(https://dev59.com/PVvUa4cB1Zd3GeqPvJpX)。 - Jesper
5个回答

2

当你想往 List<? extends Animal> 中添加一个 Cat 时,你不知道这个列表的具体类型,它也可能是一个 List<Dog>。因此,你不希望将你的 Cat 放入一个 Black Hole。这就是为什么不允许以这种方式声明的 List 进行修改。

同样地,当你从 List<? super Animal> 中获取某些内容时,你也无法知道你会得到什么。甚至可以得到一个 Object 或者一个 Animal。但是,在这个 List 中安全地添加一个 Animal 是可以的。


1

我将你的代码粘贴到我的IDE中。在do1内部发生了以下错误:

类型List中的方法add(capture#1-of ? extends Dog)不适用于参数(Collie)

这当然是预料之中的。


1

您不能将Collie添加到List<? extends Dog>中,因为此引用可能包含例如List<Spaniel>


0
这个问题的答案表明,当一个方法使用通配符泛型类型时,集合可以被访问或修改,但不能同时进行。(Kathy和Bert)
这是一个公平的第一近似,但不完全正确。更正确的是:
你只能向Collection<? extends Dog>添加null,因为它的add方法需要一个? extends Dog的参数。每当你调用一个方法时,必须传递与声明参数类型的子类型相兼容的参数;但对于参数类型? extends Dog,编译器只能确定如果表达式是null,那么参数是兼容类型。然而,你当然可以通过调用clear()remove(Object)来修改集合。
另一方面,如果你从Collection<? super Dog>中读取,它的迭代器返回类型是? super Dog。也就是说,它将返回一些未知超类型的Dog的子类型对象。但不同的是,集合可能是一个只包含String实例的Collection<Object>。因此
for (Dog d : collection) { ... } // does not compile

所以我们唯一知道的是返回 Object 的实例,即迭代此类集合的唯一类型正确方法是

for (Object o : collection) { ... }

虽然可以从集合中读取,但并不知道你将得到什么类型的对象。

我们可以将这个观察结果简单地归纳为:

class G<T> { ... }

G<? extends Something> g;

我们只能将 null 传递给声明类型为 T 的方法参数,但我们可以调用返回类型为 T 的方法,并将结果分配给类型为 Something 的变量。

另一方面,对于

G<? super Something> g;

我们可以将任何类型为Something的表达式传递给声明类型为T的方法参数,并且我们可以调用返回类型为T的方法,但只能将结果分配给类型为Object的变量。

总之,通配符类型的使用限制仅取决于方法声明的形式,而不取决于方法的实际操作。


是的,null可以分配给任何类型的任何引用,包括所有扩展Dog类型的类型。 - Joe

0

我把你的代码粘贴到IDEONE http://ideone.com/msMcQ。对我来说它没有编译成功 - 这正是我所预料的。你确定你没有任何编译错误吗?


我知道在do1方法中尝试添加元素时会出现编译器错误。我的疑问是关于Katy&Bert所说的内容。 “当方法使用通配符泛型类型时,集合可以被访问或修改,但不能同时进行。” 而且使用<? super Collie>,我可以访问并添加元素。? - Joe
@Joe。好的,我误解了你的问题,并认为你的代码已经编译通过了。现在,你没有访问do2方法中的元素。c2.size()不算是一次访问。访问应该像这样:Collie c = c2.get(0); 我修改了我的示例http://ideone.com/msMcQ,加入了能够说明问题的代码。 - emory

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接