泛型类型变量中的局部类型推断和逆变
我遇到了以下代码:
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
的泛型类型推断为T
而不是?超级T
。毕竟,根据文档,a 有一个构造函数,该构造函数接受类型的参数。因此,调用此构造函数应创建一个 ,而不是一个 。(这是第一个代码段显示的内容)。我希望遵循同样的逻辑。TreeSet<E>
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