【发布时间】:2018-04-02 18:11:15
【问题描述】:
我遇到了以下代码:
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 而不是? super T。毕竟,according to the docs,TreeSet<E> 有一个接受 Comparator<? super E> 类型参数的构造函数。所以调用这个构造函数应该创建一个TreeSet<E>,而不是TreeSet<? super E>。 (这是第一个 sn-p 显示的内容)。我希望var 遵循同样的逻辑。
注 1: 使代码编译的一种方法是将返回类型更改为 Set<? super T>。但是,那将是一个几乎无法使用的集合...
注 2: 另一种方法是在比较器中不使用逆变,但我不希望这样,因为我无法使用 Comparator 来比较T.
注 3: 我知道第一个 sn-p 有效,所以很明显我应该坚持不使用 var 并将该集合明确声明为 Set<T>。但是,我的问题不是我是否应该丢弃我的第二个 sn-p 或如何修复它。相反,我想知道为什么var 没有将TreeSet<T> 推断为我的第二个sn-p 中set 局部变量的类型。
编辑 1: 在 this comment 中,用户 @nullpointer 正确指出我应该进行以下细微更改以使第二个 sn-p 编译:
var set = new TreeSet<T>(comparator); // T brings in the magic!
现在泛型类型参数T 对TreeSet 是显式的,因此var 正确地将set 局部变量的类型推断为TreeSet<T>。不过,我想知道为什么我必须明确指定T。
编辑 2:在this other comment 中,用户@Holger 巧妙地提到以下是该语言禁止:
var set = new TreeSet<? super T>(comparator);
上面的代码编译失败,出现如下错误:
java: unexpected type
required: class or interface without bounds
found: ? super T
所以现在问题变得更加明显:如果我不能在实例化表达式new TreeSet<? super T>(comparator) 中显式指定有界泛型类型? super T,为什么编译器会推断TreeSet<? super T> 为set 局部变量的类型?
【问题讨论】:
-
评论不用于扩展讨论;这个对话是moved to chat。
-
我认为是因为涉及到两个以上的人,所以没有出现自动链接,mods必须手动执行操作。如果你真的想在这种场景下聊天,可以参考这个meta.stackexchange.com/questions/106467
-
好吧,我猜这个问题的 cmets 已经被版主或用户标记了。您可能想更新问题,链接已损坏。
标签: java generics type-inference contravariance java-10