为什么 Java 8 泛型类型推断选择这种重载?

2024-04-07

考虑以下程序:

public class GenericTypeInference {

    public static void main(String[] args) {
        print(new SillyGenericWrapper().get());
    }

    private static void print(Object object) {
        System.out.println("Object");
    }

    private static void print(String string) {
        System.out.println("String");
    }

    public static class SillyGenericWrapper {
        public <T> T get() {
            return null;
        }
    }
}

它在 Java 8 下打印“String”,在 Java 7 下打印“Object”。

我本以为这在 Java 8 中是一个歧义,因为两个重载方法都是匹配的。为什么编译器会选择print(String) after JEP 101 http://openjdk.java.net/jeps/101?

无论合理与否,这都会破坏向后兼容性,并且在编译时无法检测到更改。升级到 Java 8 后,代码的行为只是偷偷地发生了变化。

注意:SillyGenericWrapper被称为“愚蠢”是有原因的。我试图理解为什么编译器会这样做,不要告诉我愚蠢的包装器首先是一个糟糕的设计。

更新:我还尝试在 Java 8 下编译并运行该示例,但使用 Java 7 语言级别。该行为与 Java 7 一致。这是预期的,但我仍然觉得需要验证。


Java 8 中对类型推断规则进行了重大修改;最显着的是目标类型推断得到了很大改进。因此,在 Java 8 之前,方法参数站点没有收到任何推断,默认为 Object,而在 Java 8 中,会推断出最具体的适用类型,在本例中为 String。 Java 8 的 JLS 引入了新的章节第 18 章类型推断 https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.htmlJava 7 的 JLS 中缺少这一点。

JDK 1.8 的早期版本(直到 1.8.0_25)存在与重载方法解析相关的错误,当编译器成功编译代码时,根据 JLS 应产生歧义错误为什么这个方法重载不明确? https://stackoverflow.com/questions/23020493/why-is-this-method-overloading-ambiguous正如 Marco13 在评论中指出的那样

JLS 的这一部分可能是最复杂的部分

其中解释了 JDK 1.8 早期版本中的错误以及您看到的兼容性问题。


如 Java 教程中的示例所示(类型推断 https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html)

考虑以下方法:

void processStringList(List<String> stringList) {
    // process stringList
}

假设您想使用空列表调用 processStringList 方法。在 Java SE 7 中,以下语句无法编译:

processStringList(Collections.emptyList());

Java SE 7 编译器生成类似于以下内容的错误消息:

List<Object> cannot be converted to List<String>

编译器需要类型参数 T 的值,因此它以值 Object 开头。因此,Collections.emptyList 的调用返回 List 类型的值,该值与 processStringList 方法不兼容。因此,在 Java SE 7 中,您必须指定类型参数的值,如下所示:

processStringList(Collections.<String>emptyList());

这在 Java SE 8 中不再是必需的。目标类型的概念已扩展为包括方法参数,例如方法 processStringList 的参数。在这种情况下,processStringList 需要一个 List 类型的参数

Collections.emptyList()是一个类似于的通用方法get()问题中的方法。在 Java 7 中print(String string)method 甚至不适用于方法调用,因此它不参与重载决策过程。而在 Java 8 中,这两种方法都适用。

这种不兼容性值得一提JDK 8 兼容性指南 http://www.oracle.com/technetwork/java/javase/8-compatibility-guide-2156366.html.


您可以查看我对与重载方法解析相关的类似问题的回答Java 8 三元条件和未装箱原语的方法重载歧义 https://stackoverflow.com/questions/30130720/method-overload-ambiguity-with-java-8-ternary-conditional-and-unboxed-primitives/30137369#30137369

根据JLS 15.12.2.5 选择最具体的方法 https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5:

如果多个成员方法既可访问又适用于一个 方法调用时,需要选择一个来提供 运行时方法调度的描述符。 Java编程 语言使用选择最具体方法的规则。

Then:

一种适用的方法 m1 比另一种适用的方法更具体 方法 m2,用于使用参数表达式 e1, ..., ek, if 进行调用 以下任何一项为真:

  1. m2 是通用的,并且 m1 被推断为比 m2 更具体 参数表达式 e1, ..., ek 由 §18.5.4 确定。

  2. m2不是通用的,m1和m2可以严格或宽松地适用 调用,其中 m1 具有形式参数类型 S1、...、Sn 和 m2 具有形式参数类型 T1, ..., Tn,类型 Si 更具体 对于所有 i (1 ≤ i ≤ n, n = k),参数 ei 比 Ti 更重要。

  3. m2 不是通用的,m1 和 m2 通过变量数量适用 调用,其中 m1 的前 k 个变量参数类型 分别是 S1, ..., Sk 和 m2 的前 k 个可变参数类型 是 T1, ..., Tk,对于参数 ei,类型 Si 比 Ti 更具体 对于所有 i (1 ≤ i ≤ k)。另外,如果 m2 有 k+1 个参数,则 m1 的第 k+1 个变量参数类型是 m2的第k+1个可变参数类型。

上述条件是一种方法可能比另一种方法更具体的唯一情况。

对于任何表达式,如果 S <: t s>

三个选项中的第二个符合我们的情况。自从String是一个子类型Object (String <: Object)更具体。因此该方法本身是更具体。继JLS之后,这个方法也是严格更具体 and 最具体的并由编译器选择。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 Java 8 泛型类型推断选择这种重载? 的相关文章

随机推荐

  • 从另一个包导出的函数

    我正在按照以下说明进行操作https golang org doc code html Workspaces https golang org doc code html Workspaces链接 我构建了我的第一个 Go 程序 所以 我尝
  • 通过号码确定信用卡类型?

    是否可以仅根据信用卡号来确定信用卡类型 这是推荐的还是我们应该始终询问客户他们使用的信用卡类型 我用谷歌搜索了一下 发现了这个算法 http cuinl tripod com Tips o 1 htm http cuinl tripod c
  • jquery datepicker 多个实例

    我制作了一个 RequestForQuote 表格 在其中我可以添加新职位以获取 RFQ 基本上 在我的例子中 通过 PHP 可以很容易地完成这一点 工作真的很好 您可能想看一下 可以在以下位置找到 我的网站 http www thermo
  • Angular 和 google 距离矩阵没有访问控制标头

    我越来越 XMLHttpRequest 无法加载 请求中不存在 Access Control Allow Origin 标头 资源 在我的角度应用程序中 我向 google 距离矩阵发出了 http 请求 如下所示 var url http
  • 如何不断提示输入直到有效?

    我试图做到这一点 以便当给定的答案既不是 1 也不是 2 时 显示消息 请输入有效答案 出现了 又回到了问题 这是我的代码 Coloration ConsoleColor DarkMagenta What do you want to do
  • 迭代对象属性

    var obj name Simon age 20 clothing style simple hipster false for var propt in obj console log propt obj propt 变量如何propt
  • 如何在 Swing 中左对齐和右对齐组件?

    我有一个看似简单的问题 我有一些标签想要向左对齐 但是当我调整大小时 它们开始向中间漂移 这将打乱我计划添加的其他组件的对齐 我该怎么做才能让它们保持在左边 这是简短 简单的代码 不确定我的问题是什么 package com protoca
  • Proguard 找不到引用的类 com.google.ads.internal.state.AdState

    好吧 伙计们 这变得非常烦人 试图让我的项目在导出 时工作 Proguard 不断给我这个错误 Proguard returned with error code 1 See console 这是我在控制台中收到的完整错误 Warning
  • 使用 require.js 缓存时的调试

    使用 require js 我注意到 依赖项通常由浏览器缓存 即使我强制页面完全重新加载 command shift R 也不会更新 为了始终拥有更新的文件 我让 require js 要求文件在 url 后添加 datestamp 这种方
  • docker 将 Ctrl+p 更改为其他内容?

    我在用docker run bin bash开发我的容器 每次我想使用时Ctrl p在终端或 emacs 中 我必须输入两次 因为 docker 使用它来与容器分离 Ctrl p Ctrl q 我该如何改变Ctrl p到 emacs 或终端
  • 使用 shell 脚本在远程计算机上执行多个命令

    我有一个Java程序Desktop testfolder xyz jar在远程机器上 它在同一文件夹中有一个配置文件 当我通过 SSH 连接到机器时 我会 ssh user remote java cp Desktop testfolder
  • 如何在Android中使用Parse实现RecyclerView的无限滚动

    我在网上找到的大多数文章都使用 setLimit 函数来加载更多项目 但这不是一种有效的方法 因为我们会回忆现有的对象 我使用带有自定义适配器的 RecyclerView 来加载我的列表项 一旦我从 Parse 服务器收到对象列表 我就会根
  • 从树视图中选择会自动将字符串数字转换为整数

    在我正在开发的项目中 您可以编辑以树形视图形式显示的数据内容 数据字段之一是始终以 0 开头的电话号码 在表中选择正确的记录并使用其中提取数据时tree item tree selection 由于某种原因 电话号码会自动转换为整数 因此当
  • 如何使用 ACM 库 (ConsoleProgram) 编写 Java 程序?

    我想问一个关于我的程序的小问题 这是我的代码示例 public static void main String args int q int p int thelargest int thesmallest Scanner input ne
  • Angular 2 路由器路径

    我有两个关于 Angular 2 路由器路径的问题 我花了一些时间在谷歌上搜索它 但没有运气 无论如何我有以下路由设置 path contract component ContractInsertUpdateComponent childr
  • 远程片段上的引导模式“加载”事件

    我目前正在使用 Twitter Bootstrap 模式组件 并且遇到一个问题 我在使用 data remote 属性远程加载的内容中的输入字段上使用 jquery 验证插件 因为内容是在 dom 上运行 jquery 验证之后加载的 所以
  • Glassfish 3.1.1 启动失败

    我正在运行 glassfish 3 1 1 在我的开发机器 使用 Windows XP 上运行了一段时间 但今天早上它无法启动 跑步asadmin start domain domain1它报告 Waiting for domain1 to
  • RequireJS 中的命名模块与未命名模块

    我们可以通过给它命名来在 requireJS 中创建一个模块 define name dep function dep module definition 或者我们可以创建一个不包含名称的 define dep function dep m
  • 隐藏水平滚动条(Angular ui-grid)

    我试图隐藏 Angular ui grid 的水平滚动条 但我找不到正确的属性 财产启用滚动条 假删除两者 是否可以只删除水平滚动条 使用 Github v3 0 0 rc 16 上的最新版本 您可以分别禁用水平和垂直滚动条 代替 enab
  • 为什么 Java 8 泛型类型推断选择这种重载?

    考虑以下程序 public class GenericTypeInference public static void main String args print new SillyGenericWrapper get private s