当前位置: 首页 > 知识库问答 >
问题:

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

徐博雅
2023-03-14
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>

注意1:编译代码的一种方法是将返回类型更改为set<?超级T>。不过,那是一套很难用的...

注意2:另一种方法是在比较器中不使用逆变,但我不希望这样,因为我无法使用比较t祖先的比较器

注意3:我知道第一个片段起作用,所以似乎很明显,我应该坚持不使用var并将集合显式声明为set 。但是,我的问题不是我是否应该丢弃我的第二个代码段或者如何修复它。相反,我想知道为什么var没有将treeset 推断为第二个片段中的set局部变量的类型。

编辑1:在这条评论中,用户@nullpointer正确地指出,我应该进行以下细微的更改,以使第2段编译:

var set = new TreeSet<T>(comparator); // T brings in the magic!

现在,泛型类型参数t对于treeSet是显式的,因此var可以正确地将set局部变量的类型推断为treeSet 。不过,我想知道为什么必须显式指定t

编辑二:在这另一条评论中,用户@holger巧妙地提到了语言中禁止以下内容:

var set = new TreeSet<? super T>(comparator);

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

java: unexpected type
  required: class or interface without bounds
  found:    ? super T

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

共有1个答案

施文彬
2023-03-14

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

局部变量类型推断说:我需要的类型很可能已经在右手边出现了,为什么要在左边重复它们。

关于您问题中的代码,唯一可用于推断的类型(通过使用提供的比较器)是树集<?超级T>。我们人类足够聪明,能够看到distince返回set并期望一个set 。然而,编译器也可能不够聪明来解析它(我确信它可以),但更有可能的事实是var使用RHS上提供的信息来推断最特定的类型,架构师不想破坏这一点。

var set = new TreeSet<T>(comparator);

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

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

注意:“t的向上投影”,这可能只是推断出类型(treeset而不是set),但也可能包括泛型类型。

 类似资料:
  • 主要内容:Java10 局部变量类型推断,声明局部变量的旧用法,声明局部变量的新用法,需要注意的事项,Java10 局部变量类型推断的示例Java10 局部变量类型推断 局部变量类型推断是 Java 10 以后可用语言最明显的变化之一。它允许使用 var 定义变量而不指定它的类型。编译器使用提供的值推断变量的类型。这种类型推断仅限于局部变量。 声明局部变量的旧用法 声明局部变量的新用法 需要注意的事项 在成员变量、方法参数、返回值的情况下没有类型推断。 局部变量应在声明时初始化,否则编译器将无法

  • 我安装了JDK 10以试用新特性,但我对感到很困扰--出于某种原因,即使JDK被添加到IntelliJ(版本2018.1)中,以下代码仍然无法编译,说Java找不到: 我是不是遗漏了一些显而易见的东西,或者我应该启用IntelliJ中的一个选项? 编辑:项目和模块SDK和语言级别都设置为Java10安装和LVL。10(但不是级别)。

  • 本文向大家介绍Java 10中的局部变量类型推断或LVTI,包括了Java 10中的局部变量类型推断或LVTI的使用技巧和注意事项,需要的朋友参考一下 Java中的类型推断是指自动检测变量的数据类型。这种自动检测通常在编译时发生。它是Java 10的一项功能,它使开发人员可以跳过声明与局部变量关联的类型的操作。局部变量是在方法,初始化块,for循环等中定义的局部变量。类型通常由JDK标识。 直到J

  • null 这是有意义的,但是在编译时的类型是什么?它是还是其他东西?

  • 在Java10中,我们可以使用类型推断。

  • 问题内容: 除了Swift,我还有这个问题。如何Type在泛型中使用变量? 我尝试了这个: This didn’t work either: 有没有办法做到这一点?我感觉到Swift只是不支持 它,并且给了我一些模棱两可的错误消息。 编辑:这是一个更复杂的示例,其中无法 使用通用函数标头来解决问题。当然,这没有任何意义, 但是我在代码中的某处合理地使用了这种功能 ,宁愿发布一个干净的示例而不是我的