为什么 Java 8 泛型类型推断会选择这个重载? [英] Why does the Java 8 generic type inference pick this overload?

查看:24
本文介绍了为什么 Java 8 泛型类型推断会选择这个重载?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下程序:

public class GenericTypeInference {公共静态无效主(字符串 [] args){打印(新 SillyGenericWrapper().get());}私有静态无效打印(对象对象){System.out.println("对象");}私有静态无效打印(字符串字符串){System.out.println("字符串");}公共静态类 SillyGenericWrapper {公共 <T>获取(){返回空;}}}

它在 Java 8 下打印String",在 Java 7 下打印Object".

我原以为这在 Java 8 中存在歧义,因为两个重载方法都匹配.为什么编译器在 JEP 101 之后选择 print(String)?

无论是否合理,这都会破坏向后兼容性,并且在编译时无法检测到更改.升级到 Java 8 后,代码只是偷偷摸摸地表现不同.

注意:SillyGenericWrapper 被命名为silly"是有原因的.我试图理解为什么编译器的行为方式如此,首先不要告诉我愚蠢的包装器是一个糟糕的设计.

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

解决方案

Java 8 对类型推断规则进行了重大修改;最值得注意的是,目标类型推断得到了很大的改进.因此,虽然在 Java 8 之前方法参数站点没有收到任何推断,默认为 Object,但在 Java 8 中推断出最具体的适用类型,在这种情况下是 String.Java 8 的 JLS 引入了一个新章节 第 18 章.Java 7 的 JLS 中缺少的类型推断.

JDK 1.8 的早期版本(直到 1.8.0_25)有一个与重载方法解析相关的错误,当编译器成功编译代码时,根据 JLS 应该产生歧义错误 为什么这个方法重载不明确? 正如 Marco13 在评论中指出的

<块引用>

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

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

<小时>

如 Java 教程中的示例所示 (类型推断)

<块引用>

考虑以下方法:

void processStringList(List stringList) {//处理字符串列表}

<块引用>

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

processStringList(Collections.emptyList());

<块引用>

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

List无法转换为 List

<块引用>

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

processStringList(Collections.emptyList());

<块引用>

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

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

这种不兼容性在兼容性指南中值得一提对于 JDK 8.

<小时>

您可以查看我对与重载方法解析相关的类似问题的回答 Java 8 三元条件和未装箱原语的方法重载歧义

根据 JLS15.12.2.5 选择最具体的方法:

<块引用>

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

那么:

<块引用>

一种适用的方法 m1 比另一种适用的方法更具体方法 m2,用于带有参数表达式 e1, ..., ek, if 的调用以下任一情况为真:

  1. m2 是通用的,推断 m1 比 m2 更具体参数表达式 e1, ..., ek by §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 是适用于变量arity调用,其中m1的前k个变量arity参数类型是 S1, ..., Sk 和 m2 的前 k 个变量 arity 参数类型是 T1, ..., Tk,对于参数 ei,类型 Si 比 Ti 更具体对于所有 i (1 ≤ i ≤ k).此外,如果 m2 有 k+1 个参数,则m1的第k+1个变量arity参数类型是m2的第k+1个变量arity参数类型.

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

对于任何表达式,如果 S <: T,则类型 S 比类型 T 更具体(第 4.10 节).

三个选项中的第二个符合我们的情况.由于 StringObject (String <: Object) 的子类型,所以它更具体.因此,方法本身更具体.遵循 JLS,此方法也更具体最具体,并由编译器选择.

Consider the following program:

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;
        }
    }
}

It prints "String" under Java 8 and "Object" under Java 7.

I would have expected this to be an ambiguity in Java 8, because both overloaded methods match. Why does the compiler pick print(String) after JEP 101?

Justified or not, this breaks backward compatibility and the change cannot be detected at compile time. The code just sneakily behaves differently after upgrading to Java 8.

NOTE: The SillyGenericWrapper is named "silly" for a reason. I'm trying to understand why the compiler behaves the way it does, don't tell me that the silly wrapper is a bad design in the first place.

UPDATE: I've also tried to compile and run the example under Java 8 but using a Java 7 language level. The behavior was consistent with Java 7. That was expected, but I still felt the need to verify.

解决方案

The rules of type inference have received a significant overhaul in Java 8; most notably target type inference has been much improved. So, whereas before Java 8 the method argument site did not receive any inference, defaulting to Object, in Java 8 the most specific applicable type is inferred, in this case String. The JLS for Java 8 introduced a new chapter Chapter 18. Type Inference that's missing in JLS for Java 7.

Earlier versions of JDK 1.8 (up until 1.8.0_25) had a bug related to overloaded methods resolution when the compiler successfully compiled code which according to JLS should have produced ambiguity error Why is this method overloading ambiguous? As Marco13 points out in the comments

This part of the JLS is probably the most complicated one

which explains the bugs in earlier versions of JDK 1.8 and also the compatibility issue that you see.


As shown in the example from the Java Tutoral (Type Inference)

Consider the following method:

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

Suppose you want to invoke the method processStringList with an empty list. In Java SE 7, the following statement does not compile:

processStringList(Collections.emptyList());

The Java SE 7 compiler generates an error message similar to the following:

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

The compiler requires a value for the type argument T so it starts with the value Object. Consequently, the invocation of Collections.emptyList returns a value of type List, which is incompatible with the method processStringList. Thus, in Java SE 7, you must specify the value of the value of the type argument as follows:

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

This is no longer necessary in Java SE 8. The notion of what is a target type has been expanded to include method arguments, such as the argument to the method processStringList. In this case, processStringList requires an argument of type List

Collections.emptyList() is a generic method similar to the get() method from the question. In Java 7 the print(String string) method is not even applicable to the method invocation thus it doesn't take part in the overload resolution process. Whereas in Java 8 both methods are applicable.

This incompatibility is worth mentioning in the Compatibility Guide for JDK 8.


You can check out my answer for a similar question related to overloaded methods resolution Method overload ambiguity with Java 8 ternary conditional and unboxed primitives

According to JLS 15.12.2.5 Choosing the Most Specific Method:

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

Then:

One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:

  1. m2 is generic, and m1 is inferred to be more specific than m2 for argument expressions e1, ..., ek by §18.5.4.

  2. m2 is not generic, and m1 and m2 are applicable by strict or loose invocation, and where m1 has formal parameter types S1, ..., Sn and m2 has formal parameter types T1, ..., Tn, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).

  3. m2 is not generic, and m1 and m2 are applicable by variable arity invocation, and where the first k variable arity parameter types of m1 are S1, ..., Sk and the first k variable arity parameter types of m2 are T1, ..., Tk, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ k). Additionally, if m2 has k+1 parameters, then the k+1'th variable arity parameter type of m1 is a subtype of the k+1'th variable arity parameter type of m2.

The above conditions are the only circumstances under which one method may be more specific than another.

A type S is more specific than a type T for any expression if S <: T (§4.10).

The second of three options matches our case. Since String is a subtype of Object (String <: Object) it is more specific. Thus the method itself is more specific. Following the JLS this method is also strictly more specific and most specific and is chosen by the compiler.

这篇关于为什么 Java 8 泛型类型推断会选择这个重载?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆