我遇到了以下代码:
public static <T> Set<T> distinct(
Collection<? extends T> list,
Comparator<? super T> comparator) {
Set<T> set = new TreeSet<>(comparator);
set.addAll(list);
return set;
}
此代码仅使用中间体TreeSet
来删除重复项,其中元素之间的相等性是根据提供的比较器定义的。
让我们给本地类型推断一个机会,我(天真地)认为......所以我将上面的代码更改为:
public static <T> Set<T> distinct(
Collection<? extends T> list,
Comparator<? super T> comparator) {
var set = new TreeSet<>(comparator);
set.addAll(list);
return set;
}
这对我来说是有道理的,因为set
可以从 的类型推断出 的类型comparator
,或者我是这么认为的。但是,修改后的代码无法编译并生成以下错误:
java: incompatible types: java.util.TreeSet<capture#1 of ? super T> cannot be converted to java.util.Set<T>
现在,我明白了为什么会发生错误,并且我承认比较器的类型实际上是Comparator<? super T>
,所以推断的类型var
是TreeSet<? super T>
。
但是,我想知道为什么var
不能将泛型类型推断为TreeSet
asT
而不是? super T
. 毕竟,根据文档, aTreeSet<E>
有一个接受 type 参数的构造函数Comparator<? super E>
。所以调用这个构造函数应该创建一个TreeSet<E>
,而不是一个TreeSet<? super E>
。(这是第一个片段显示的内容)。我希望var
遵循同样的逻辑。
注意 1:使代码编译的一种方法是将返回类型更改为Set<? super T>
. 但是,那将是一个几乎无法使用的集合...
注 2:另一种方法是不在比较器中使用逆变,但我不希望这样做,因为我无法使用Comparator
比较T
.
注意 3:我知道第一个片段有效,所以很明显我应该坚持不使用var
并将集合明确声明为Set<T>
. 但是,我的问题不是我是否应该丢弃我的第二个片段或如何修复它。相反,我想知道为什么不在我的第二个片段中var
推断TreeSet<T>
为局部变量的类型。set
编辑1:在此评论中,用户@nullpointer 正确指出我应该进行以下细微更改以编译第二个片段:
var set = new TreeSet<T>(comparator); // T brings in the magic!
现在泛型类型参数T
对于 是显式的TreeSet
,因此可以正确地将局部变量var
的类型推断为。不过,我想知道为什么我必须明确指定。set
TreeSet<T>
T
编辑 2:在另一条评论中,用户 @Holger 巧妙地提到该语言禁止以下内容:
var set = new TreeSet<? super T>(comparator);
上面的代码编译失败,出现以下错误:
java: unexpected type
required: class or interface without bounds
found: ? super T
所以现在问题变得更加明显:如果我不能? super T
在实例化表达式中明确指定有界泛型类型new TreeSet<? super T>(comparator)
,为什么编译器会推断TreeSet<? super T>
为set
局部变量的类型?