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

标签 java generics java-8 language-lawyer

考虑以下程序:

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 中的歧义,因为两个重载方法都匹配。为什么编译器在 JEP 101 之后选择 print(String)

无论合理与否,这都会破坏向后兼容性,并且无法在编译时检测到更改。升级到 Java 8 后,代码的行为会有所不同。

注意:SillyGenericWrapper 被命名为“silly”是有原因的。我试图理解为什么编译器会以它的方式运行,请不要告诉我愚蠢的包装器首先是一个糟糕的设计。

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

最佳答案

类型推断规则在 Java 8 中得到了重大改进;最值得注意的是目标类型推断得到了很大改进。因此,在 Java 8 之前方法参数站点没有收到任何推断,默认为 Object,在 Java 8 中推断出最具体的适用类型,在本例中为 String。 JLS for Java 8 引入了一个新章节 Chapter 18. Type Inference Java 7 的 JLS 中缺少这一点。

早期版本的 JDK 1.8(直到 1.8.0_25)有一个与重载方法解析相关的错误,当编译器成功编译根据 JLS 应该产生歧义错误的代码时 Why is this method overloading ambiguous?正如 Marco13 在评论中指出的那样

This part of the JLS is probably the most complicated one

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


如 Java 教程 (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()是类似于 get() 的通用方法从问题的方法。 在 Java 7 中 print(String string)方法甚至不适用于方法调用,因此它不参与重载解析过程。而在 Java 8 中,这两种方法都适用。

这种不兼容性在 Compatibility Guide for JDK 8 中值得一提.


您可以查看我对与重载方法解析相关的类似问题的回答 Method overload ambiguity with Java 8 ternary conditional and unboxed primitives

根据 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.

然后:

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).

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

关于java - 为什么 Java 8 泛型类型推断选择这个重载?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40154690/

相关文章:

java - Java中仅显示最后的数字和

java - 强制两个参数化类型不同java

collections - 如何根据 Java 8 中列表元素内的比较来过滤列表?

java - 如何将 Map<T,List<L>> 映射中的值添加到 List<L>?

java - 如何停止重复数字?

java.lang.ClassNotFoundException : org. apache.commons.fileupload.FileItemFactory

python - 如何在 python 中实例化类型提示的类?

java - 如何获得<? super T> 来自 T

java - 仅使用带有 lambda 的 Collection.sort() 对对象列表进行排序

java - 如何创建通用分页拆分器?