给出下面的例子(使用JUnit和Hamcrest匹配器):

Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));  

它不使用JUnit assertThat方法签名编译:

public static <T> void assertThat(T actual, Matcher<T> matcher)

编译器错误消息是:

Error:Error:line (102)cannot find symbol method
assertThat(java.util.Map<java.lang.String,java.lang.Class<java.util.Date>>,
org.hamcrest.Matcher<java.util.Map<java.lang.String,java.lang.Class
    <? extends java.io.Serializable>>>)

但是,如果我将assertThat方法签名更改为:

public static <T> void assertThat(T result, Matcher<? extends T> matcher)

然后进行编译工作。

所以有三个问题

Why exactly doesn't the current version compile? Although I vaguely understand the covariance issues here, I certainly couldn't explain it if I had to. Is there any downside in changing the assertThat method to Matcher<? extends T>? Are there other cases that would break if you did that? Is there any point to the genericizing of the assertThat method in JUnit? The Matcher class doesn't seem to require it, since JUnit calls the matches method, which is not typed with any generic, and just looks like an attempt to force a type safety which doesn't do anything, as the Matcher will just not in fact match, and the test will fail regardless. No unsafe operations involved (or so it seems).

作为参考,下面是assertThat的JUnit实现:

public static <T> void assertThat(T actual, Matcher<T> matcher) {
    assertThat("", actual, matcher);
}

public static <T> void assertThat(String reason, T actual, Matcher<T> matcher) {
    if (!matcher.matches(actual)) {
        Description description = new StringDescription();
        description.appendText(reason);
        description.appendText("\nExpected: ");
        matcher.describeTo(description);
        description
            .appendText("\n     got: ")
            .appendValue(actual)
            .appendText("\n");

        throw new java.lang.AssertionError(description.toString());
    }
}

当前回答

这可以归结为:

Class<? extends Serializable> c1 = null;
Class<java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date

您可以看到Class引用c1可以包含一个Long实例(因为给定时间的底层对象可能是List<Long>),但显然不能转换为Date,因为不能保证“未知”类是Date。它不是类型安全的,所以编译器不允许它。

然而,如果我们引入一些其他对象,比如List(在你的例子中,这个对象是Matcher),那么下面的情况就会成立:

List<Class<? extends Serializable>> l1 = null;
List<Class<java.util.Date>> l2 = null;
l1 = l2; // wont compile
l2 = l1; // wont compile

...但是,如果List的类型变成?扩展T而不是T....

List<? extends Class<? extends Serializable>> l1 = null;
List<? extends Class<java.util.Date>> l2 = null;
l1 = l2; // compiles
l2 = l1; // won't compile

我认为通过将Matcher<T>更改为Matcher<?扩展T>,你基本上引入了类似于赋值l1 = l2的场景;

嵌套通配符仍然很令人困惑,但希望这有助于理解为什么通过查看如何将泛型引用分配给彼此来理解泛型。当你调用函数时,编译器会推断T的类型(你没有显式地告诉它是T是),这也让人更加困惑。

其他回答

如果你使用

Map<String, ? extends Class<? extends Serializable>> expected = null;

这可以归结为:

Class<? extends Serializable> c1 = null;
Class<java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date

您可以看到Class引用c1可以包含一个Long实例(因为给定时间的底层对象可能是List<Long>),但显然不能转换为Date,因为不能保证“未知”类是Date。它不是类型安全的,所以编译器不允许它。

然而,如果我们引入一些其他对象,比如List(在你的例子中,这个对象是Matcher),那么下面的情况就会成立:

List<Class<? extends Serializable>> l1 = null;
List<Class<java.util.Date>> l2 = null;
l1 = l2; // wont compile
l2 = l1; // wont compile

...但是,如果List的类型变成?扩展T而不是T....

List<? extends Class<? extends Serializable>> l1 = null;
List<? extends Class<java.util.Date>> l2 = null;
l1 = l2; // compiles
l2 = l1; // won't compile

我认为通过将Matcher<T>更改为Matcher<?扩展T>,你基本上引入了类似于赋值l1 = l2的场景;

嵌套通配符仍然很令人困惑,但希望这有助于理解为什么通过查看如何将泛型引用分配给彼此来理解泛型。当你调用函数时,编译器会推断T的类型(你没有显式地告诉它是T是),这也让人更加困惑。

我理解通配符的一种方法是认为通配符并没有指定给定泛型引用可以“拥有”的可能对象的类型,而是它与其他泛型引用兼容的类型(这听起来可能令人困惑…)因此,第一个答案的措辞非常具有误导性。

换句话说,List<?扩展Serializable>意味着您可以将该引用赋值给其他列表,其中类型为某个未知类型或Serializable的子类。不要认为它是一个单一的列表,可以容纳Serializable的子类(因为这是不正确的语义,会导致对泛型的误解)。

我知道这是一个老问题,但我想分享一个例子,我认为它很好地解释了有界通配符。collections提供了以下方法:

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

If we have a List of T, the List can, of course, contain instances of types that extend T. If the List contains Animals, the List can contain both Dogs and Cats (both Animals). Dogs have a property "woofVolume" and Cats have a property "meowVolume." While we might like to sort based upon these properties particular to subclasses of T, how can we expect this method to do that? A limitation of Comparator is that it can compare only two things of only one type (T). So, requiring simply a Comparator<T> would make this method usable. But, the creator of this method recognized that if something is a T, then it is also an instance of the superclasses of T. Therefore, he allows us to use a Comparator of T or any superclass of T, i.e. ? super T.

您的原始代码不能编译的原因是<?>的意思不是“任何扩展Serializable的类”,而是“一些未知但特定的扩展Serializable的类”。

例如,给定所编写的代码,将新的TreeMap<String, Long.class>()赋值给expected是完全有效的。如果编译器允许代码编译,assertThat()可能会中断,因为它期望的是Date对象,而不是它在映射中发现的Long对象。