Collections.max()签名的实际使用

Nar*_*hai 9 java generics wildcard

这是签名解释的Collections.max()后续问题,其中接受的答案并未涉及此通配符的实际原因.

max方法需要一个Collection<? extends T>,我无法想到这个通配符有用的实际案例.

我甚至通过通配符提到Oracle更有趣,它说明了这一点

通常,如果您的API只使用类型参数T作为参数,则其使用应该利用较低的有界通配符(?super T).相反,如果API仅返回T,您将通过使用上限有通配符(?extends T)为您的客户提供更大的灵活性.

但我仍然没有得到它.甚至Java Generics and Collections一书也没有说明这个通配符背后的原因.

这有实际用途吗?一个真实世界的用例会很棒.

Luk*_*der 5

想象一下这两个签名:

static <T extends Object & Comparable<? super T>> T max1(Collection<T> coll);
static <T extends Object & Comparable<? super T>> T max2(Collection<? extends T> coll);
Run Code Online (Sandbox Code Playgroud)

现在,让我们尝试构造类型的函数.

Function<Collection<java.sql.Timestamp>, java.util.Date>

// This doesn't work:
Function<Collection<Timestamp>, Date> f0 = 
    (Collection<Timestamp> col) -> Test.<Date>max1(col);

// This works:
Function<Collection<Timestamp>, Date> f1 = 
    (Collection<Timestamp> col) -> Test.<Date>max2(col);
Run Code Online (Sandbox Code Playgroud)

如您所见,使用显式类型,其中一种方法不再起作用.

类型推断在"现实世界"中"隐藏"了这个问题:

当然,您可以省略泛型类型参数的显式绑定Test.<Date>maxN():

Function<Collection<Timestamp>, Date> f0 = 
    (Collection<Timestamp> col) -> Test.max1(col);
Function<Collection<Timestamp>, Date> f1 = 
    (Collection<Timestamp> col) -> Test.max2(col);
Run Code Online (Sandbox Code Playgroud)

甚至:

Function<Collection<Timestamp>, Date> f2 = Test::max1;
Function<Collection<Timestamp>, Date> f3 = Test::max2;
Run Code Online (Sandbox Code Playgroud)

并且类型推断将发挥其神奇作用,因为以上所有编译.

现在为什么要打扰?

正如pbabcdefp在他的回答中所说,我们应该总是对API的一致性感到烦恼.想象一下,还有一个额外的optionalMax()方法(只是为了处理空参数集合的情况):

static <T extends ...> Optional<T> optionalMax1(Collection<T> coll);
static <T extends ...> Optional<T> optionalMax2(Collection<? extends T> coll);
Run Code Online (Sandbox Code Playgroud)

现在,您可以清楚地看到只有第二个变体是有用的:

// Does not work:
Function<Collection<Timestamp>, Optional<Date>> f0 = 
    (Collection<Timestamp> col) -> Test.optionalMax1(col);
Function<Collection<Timestamp>, Optional<Date>> f2 = Test::max1;

// Works:
Function<Collection<Timestamp>, Optional<Date>> f1 = 
    (Collection<Timestamp> col) -> Test.optionalMax2(col);
Function<Collection<Timestamp>, Optional<Date>> f3 = Test::optionalMax2;
Run Code Online (Sandbox Code Playgroud)

考虑到这一点,以下API会感觉非常不一致,因而错误:

static <T extends ...> T max(Collection<T> coll);
static <T extends ...> Optional<T> optionalMax(Collection<? extends T> coll);
Run Code Online (Sandbox Code Playgroud)

因此,为了保持一致(无论是否需要输入),方法应该使用Collection<? extends T>签名.总是.


Pau*_*ton 4

一个有用的实验是这样的:

static <T extends Object & Comparable<? super T>> T max1(Collection<T> coll) {
    return max2(coll);
}

static <T extends Object & Comparable<? super T>> T max2(Collection<? extends T> coll) {
    return max1(coll);
}
Run Code Online (Sandbox Code Playgroud)

编译的事实表明这两个签名接受完全相同的参数集。

事实上,Collections类的签名中有几个多余的通配符。

例如,不需要通配符

static <T> void fill(List<? super T> list, T t)
Run Code Online (Sandbox Code Playgroud)

static <T> void copy(List<? super T> dst, List<? extends T> src)
Run Code Online (Sandbox Code Playgroud)

如果一个或另一个(但不是两个)通配符不存在,也会同样有效。

那么为什么要包括它们呢?

这个copy例子是一个很好的例子。super强调的使用dst是在只读的意义上使用的,extends强调的使用src是在只读的意义上使用的。同样fill,在 中super,虽然不需要,但它强调list正在写入,但未读取。在 中maxextends强调正在List读取但未写入。对于Collection既可以读取又可以写入的方法,例如

static <T extends Comparable<? super T>> void sort(List<T> list)
Run Code Online (Sandbox Code Playgroud)

不使用通配符。

总而言之,我认为通配符没有任何实际用途,但它是指示该Collection方法使用 的方式的好方法。