泛型类型变量中的局部类型推断和逆变

Fed*_*ner 15 java generics type-inference contravariance java-10

我来自以下代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

此代码仅使用中间项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;
}
Run Code Online (Sandbox Code Playgroud)

这对我来说很有意义,因为set可以从类型中推断出类型comparator,或者我认为.但是,修改后的代码不会编译并生成以下错误:

java: incompatible types: java.util.TreeSet<capture#1 of ? super T> cannot be converted to java.util.Set<T>
Run Code Online (Sandbox Code Playgroud)

现在,我理解为什么会出现错误,并且我承认比较器Comparator<? super T>的类型实际上是,所以推断的类型varTreeSet<? super T>.

不过,我不知道为什么var不能推断出通用型的TreeSet作为只是T代替? super 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!
Run Code Online (Sandbox Code Playgroud)

现在泛型类型参数T是显式的TreeSet,因此var正确地将set局部变量的类型推断为TreeSet<T>.不过,我想知道为什么我必须明确T指出.


编辑2:另一条评论中,用户@Holger巧妙地提到语言中禁止以下内容:

var set = new TreeSet<? super T>(comparator);
Run Code Online (Sandbox Code Playgroud)

上面的代码无法编译,出现以下错误:

java: unexpected type
  required: class or interface without bounds
  found:    ? super T
Run Code Online (Sandbox Code Playgroud)

所以现在问题变得更加明显:如果我不能? super T在实例化表达式中明确指定有界泛型类型new TreeSet<? super T>(comparator),为什么编译器会推断TreeSet<? super T>set局部变量的类型?

Jac*_* G. 8

根据Brian Goetz我提问的回答,他说:

局部变量类型推断说:我需要的类型可能已经存在于右侧,为什么在左侧重复它们.

关于您问题中的代码,唯一可用于推断的类型(通过使用Comparator提供的)是TreeSet<? super T>.我们人类足够聪明,可以看到distinct回报,set并期待一个Set<T>.但是,编译器要么可能不够聪明才能解决它(我确信它可以),但更可能是var使用RHS上提供的信息推断出最具体的类型,而架构师不希望打破那个.

现在,作为nullpointer在其注释中声明,您可以使用以下内容明确定义您TreeSet的类型T而不是推断的捕获类型? super T:

var set = new TreeSet<T>(comparator);
Run Code Online (Sandbox Code Playgroud)

我假设显式泛型类型覆盖传递给构造函数的推断类型,这是有意义的.

JLS§14.4.1:本地变量声明符和类型似乎支持我的声明,说明如下:

在此输入图像描述

注意:"T的向上投影",可能只是推断类型(TreeSet而不是Set),但也可能包括泛型类型.

我相信这是一样的道理,为什么listvar list = List.<Number>of(1, 2, 3);一个List<Number>而不是一个List<Integer>,它的作品一样好没有var.

  • @FedericoPeraltaSchaffner每次我们,开发者,发现这样的情况,往往会想到为什么不在这里呢?我已经多次听过这个论点了,我想这可能已经完成了,但它要么看起来不那么简单,要么会带来更复杂的代码,对于一个不常见的情况.我猜... (3认同)
  • 我相信它*可以*足够聪明,但是,正如迈克刚才所说,没有理由它应该向前看,因为它只使用同一行的RHS上的信息来推断出类型.如果这是未来的修改,我一定会欢迎它! (2认同)