Java 泛型(通配符)

126

我有几个关于Java中通配符的问题:

  1. List<? extends T>List<? super T>之间有什么区别?

  2. 什么是有界通配符,什么是无界通配符?


1
如果Ted Gao回答被删除(因为它只是一个链接),这里是它所链接的博客文章 - royhowie
7个回答

137
在你的第一个问题中,<? extends T><? super T>是有界通配符的例子。无界通配符看起来像<?>,基本上表示<? extends Object>。它松散地表示泛型可以是任何类型。有界通配符(<? extends T><? super T>)通过指定必须要么扩展特定类型(<? extends T>称为上限),要么是特定类型的祖先(<? super T>称为下限)来对类型进行限制。
Java教程在文章WildcardsMore Fun with Wildcards中对泛型有一些很好的解释。

只是为了澄清,如果 A < B 且 B < C,则 <A extends C> 是错误的吗? - Pablo Fernandez
如果你说A < B,意思是A扩展B,那么A确实扩展了C。但在通配符语法中,你不会使用这个,而是要使用<? extends C>来限制你的选择为A或B。 - Bill the Lizard
如果我这样说 <? super C> 会有什么区别? - Pablo Fernandez
3
我建议您参考另一个关于Java泛型的资料:http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html。 - Zach Scrivena
4
<? super C>的意思是你的类型被限制为类型层次结构中位于C上方的某些类型。很抱歉回复晚了,我想两年前我们还没有评论通知功能? - Bill the Lizard

54
如果您有一个类层次结构 ABA 的子类,而 CD 都是 B 的子类,则如下所示。
class A {}
class B extends A {}
class C extends B {}
class D extends B {}

然后

List<? extends A> la;
la = new ArrayList<B>();
la = new ArrayList<C>();
la = new ArrayList<D>();

List<? super B> lb;
lb = new ArrayList<A>(); //fine
lb = new ArrayList<C>(); //will not compile

public void someMethod(List<? extends B> lb) {
    B b = lb.get(0); // is fine
    lb.add(new C()); //will not compile as we do not know the type of the list, only that it is bounded above by B
}

public void otherMethod(List<? super B> lb) {
    B b = lb.get(0); // will not compile as we do not know whether the list is of type B, it may be a List<A> and only contain instances of A
    lb.add(new B()); // is fine, as we know that it will be a super type of A 
}

一个有界通配符就像 ? extends B,其中 B 是某种类型。也就是说,该类型是未知的,但是可以对其进行“限定”。在这种情况下,它被某个类所限定,该类是B的子类。


我认为 List<? super B> 的描述是 List 接受的类型是类 B 的父类?这就是为什么 C 和 D 不能编译的原因吗? - knoxgon

39

在这个Google IO 视频演讲中,Josh Bloch 也对何时使用 superextends 作了很好的解释,并提到了“生产者 extends 消费者 super”这个助记符。

来自演示文稿:

假设您想要向 Stack<E> 添加批量方法

void pushAll(Collection<? extends E> src);

– src 是一个生产 E 的对象

void popAll(Collection<? super E> dst);

– dst 是一个消费 E 的对象


1
我已经读了Bloch的书,但在这种特定情况下,我仍然看不出extends和super之间的区别。 - Pablo Fernandez
看这个视频,我觉得很清楚。此外,我认为你应该提出另一个问题:“List<? extends T>和List<? super T>有什么区别”,希望你可以得到更多的回答。(如果你这样做了,请在这里添加链接) - blank
3
床 - 为什么不在这个答案中包含示例,使其完整且独立。链接是短暂的。 - James Schek

4

有时候,您可能希望限制可以传递给类型参数的类型。例如,一个操作数字的方法可能只想接受Number或其子类的实例。这就是有界类型参数的作用。

Collection<? extends MyObject> 

意味着它可以接受所有与MyObject具有IS-A关系的对象(即任何类型为myObject的子类或者我们可以说是任何MyObject类的对象),或者一个MyObject类的对象。

例如:

class MyObject {}

class YourObject extends MyObject{}

class OurObject extends MyObject{}

然后,
Collection<? extends MyObject> myObject; 

只接受MyObject或者MyObject的子类(例如OurObject或YourObject或MyObject类型的任何对象,但不包括MyObject的超类的任何对象)。


1
一般来说,
如果一个结构包含类型形式为? extends E的元素,我们可以从结构中获取元素,但无法将元素放入结构中。
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(3.14); // compile-time error
assert ints.toString().equals("[1, 2, 3.14]"); 

为了将元素放入结构中,我们需要另一种名为“带有super的通配符”的通配符。
 List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
    List<Integer> ints = Arrays.asList(5, 6);
    Collections.copy(objs, ints);
    assert objs.toString().equals("[5, 6, four]");

    public static <T> void copy(List<? super T> dst, List<? extends T> src) {
          for (int i = 0; i < src.size(); i++) {
                dst.set(i, src.get(i));
         }
    }

1

通配符用于创建更具可重用性的操作Collection的方法。

例如,如果一个方法有一个参数List<A>,我们只能给这个方法提供List<A>。在某些情况下,这种方法的功能是浪费的:

  1. 如果这个方法只从List<A>中读取对象,则应该允许将List<A-sub>提供给该方法。(因为A-sub是一个A)
  2. 如果这个方法只向List<A>中插入对象,则应该允许将List<A-super>提供给该方法。(因为A是一个A-super)

0

通过示例学习:

考虑在Collections类中使用既有extends又有supersort()方法:

    public static <T extends Comparable<? super T>> void sort(List<T> list){...}

所以

为什么要使用 <T extends Comparable<...>>: 因为我们需要列表项 (T) 是 Comparable 接口的子类。

为什么要使用 Comparable<? super T>: 因为我们允许 Comparable 类型是 T 的任何超类型的 Comparable

考虑一下

interface Comparable<T>{
    public int compareTo(T o);
}

public static <T extends Comparable<? super T>> void sort(List<T> list){...}


public static <T extends Comparable<T>> void sort2(List<T> list){...}


class A implements Comparable<A>{
    @Override
    public int compareTo(A o) {
        ...
    }
}

class B extends A {
}

    List<A> listA = new ArrayList<>();
    List<B> listB = new ArrayList<>();

    sort(listA);  //ok
    sort(listB);  //ok
    
    sort2(listA); //ok
    sort2(listB); //Error

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