Java泛型通配符

8

我对Java泛型中通配符的使用有疑问: List<? extends Set>List<T extends Set> 之间的基本区别是什么?在何时会使用它们?


1
一个更好的问题是这些之间以及List之间有什么区别... - Blindy
4
@Blindy:那是完全不同的问题。 - blubb
List<T extends Set> 不是有效的语法。 - newacct
5个回答

9

有两个原因:

避免不必要的强制类型转换:

对于这种情况,您必须使用 T 变体:

public <T extends Set> T firstOf(List<T> l) {
    return l.get(0);
}

使用 ?,这将变为:

public Set firstOf2(List<? extends Set> l) {
    return l.get(0);
}

...这并不能给firstOf方法的调用者提供相同数量的信息。第一个版本允许调用者执行以下操作:

SubSet first = firstOf(listOfSubSet);

使用第二个版本时,你必须使用强制类型转换才能使其编译通过:
SubSet first = (SubSet)firstOf(listOfSubSet);

强制匹配参数类型:

public <T extends Set> boolean compareSets(List<T> a, List<T> b) {
    boolean same = true;
    for(T at : a) {
        for (T bt: b) {
             same &= at.equals(bt);
        }
    }
    return same;
}

在这种情况下,没有直接相当于使用 ? 替换 T 的方法。请注意,由于 Java 的单分派,在上面的版本中,编译器将调用 atequals(T) 方法,这可能与 atequals(Set)equals(Object) 方法不同。


好的例子。另一个重要的点是,虽然在这种情况下名称表明了它,但返回Set而不是T extends Set的方法版本在其合同中没有公开返回给定List包含的相同类型的信息,这更普遍地意味着您不能依赖于该信息是正确的。 - ColinD
由于我们知道Set没有compare方法,因此很难理解您在第二个示例中的意思。如果要向Set添加compare方法以完成您的示例,则它们的签名会是什么样子?(确保在展示代码之前进行编译) - irreputable
@irreputable:很好的发现,我是指“equals”而不是其他。 - blubb
如果ab被声明为List<? extends Set>,那么equals(Set)方法将被调用。 - irreputable

1

这里的区别在于第二个版本中,你有一个类型变量T,它指的是List包含的特定子类型Set。在需要确保其他内容与列表中包含的类型相同的情况下,您需要这个变量。以下是一些简单的例子:

// want to ensure that the method returns the same type contained in the list
public <T extends Set> T something(List<T> list) {
  ...
}

// want to ensure both lists contain the exact same type
public <T extends Set> List<T> somethingElse(List<T> first, List<T> second) {
  ...
}

简单规则:如果同一类型在两个地方都需要使用,请在方法签名中使用类型变量T extends Foo。方法参数是其中一个地方,方法返回类型是另一个地方。如果您只需要确保在一个地方处理“某些东西是Foo”,请使用通配符? extends Foo

另外:不要使用原始类型Set


0

我们使用通配符来指定类型元素匹配任何内容。 ? 代表未知类型。

List<? extends Set> 是一个有界通配符的示例,它表示列表可以接受 任何 Set 的子类型(例如 HashSet)。 List<T extends Set> 则允许将 T 限制 为扩展 Set 的类型。

当我需要一个数据集合而不考虑其确切类型时,我会使用通配符。


"add(? extends Set)"在语义上等同于"add(Set)"。那么,为什么要使用"List<? extends Set>"而不是"List<Set>"呢? - blubb
你是说你示例中的代码可以工作吗?因为那样是无法编译通过的(只有null可以用于带通配符? extends Set的参数)。你也不能创建一个new ArrayList<? extends Set>() - ColinD
@ColinD,是的,代码从来都不会一次就成功...抱歉,我已经删除了我的错误示例。 - Buhake Sindi

0

在声明变量时,您可以使用 List<? extends Set>。例如:

List<? extends Number> l = new ArrayList<Integer>();

List<T extends Number> 可以在类或方法声明中使用。这将允许您在函数中稍后写 T 而不是 <? extends Number>

public <T extends Number> int addAll(List<T> list) {
    int result = 0;
    for (T t : list) {
        result += t.intValue();
    }
    return result;
}

3
当你创建一个new ArrayList<Integer>()时,基本上不应该声明一个类型为 List<? extends Number>的变量。只需将其分配给List<Integer>即可。通配符用于方法参数,以确保您可以在任何想要使用List<Integer>的地方使用它。 - ColinD
2
此外,你的 addAll 方法可以很好地处理类型为 List<? extends Number> 的参数(你实际上根本没有使用 T),因此这不是一个好的例子。 - ColinD
当然,在简单的例子中,你几乎不需要T。最多只是让代码更清晰一些。只有当你对T应用双重限制T extends Object & Comparable<? super T>时,T才变得有用。在类中使用有界通配符也更为常见,List被声明为public interface List<E> extends Collection<E>,并且具有各种以E作为参数或返回类型的函数E get(int index);。我现在真的想不出一个好的1个方法的例子了。 - Dorus

-1

通配符类型 G<? extends A> 是任何 G<Ai> 的超类型,其中 AiA 的子类型。

换句话说,G<? extends A>G<A0>, ..., G<An> 的联合类型。


问题是 T extends A? extends A 之间的区别以及何时使用其中之一,而不是首先解释 ? extends A 是什么。 - blubb
@Simon 真正的问题在于人们不知道通配符是什么。他们只是在特定的用例中遵循一些经验法则,而不理解其中的原因,比如你提供的方法签名的经验法则只是其中之一。 - irreputable
我相信人们可以表达自己的想法。如果不是这样,我认为Stack Overflow就不会像现在这么有用。话虽如此,人们确实知道通配符是什么。我曾经有一位优秀的类型系统教授承认他很少使用<?>,并且这几乎引发了本文所讨论的所有议题。 - blubb
此外,如果我觉得原帖作者真的想知道通配符是什么,我会将他重定向到一个实际陈述这一点并在那里给出答案的问题。 - blubb

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