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

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

问题描述

考虑下面的程序:

  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;


$ b $ / code $ / pre
$ b $ p打印下面的String Java 8和Java 7下的对象。

我会认为这是Java 8中的一个模棱两可的问题,因为两种重载方法都是匹配的。为什么编译器在 JEP 101 之后选择 print(String)

合理与否,这会破坏向后兼容性,并且在编译时无法检测到更改。注意: SillyGenericWrapper 被命名为愚蠢,因为它的行为不同于在升级到Java 8后的行为。 一个原因。我试图理解为什么编译器的行为如此,不要告诉我愚蠢的包装首先是一个糟糕的设计。



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

推理在Java 8中得到了重大改进;最值得注意的是目标类型推断得到了很大改善。因此,在Java 8之前,方法参数站点没有得到任何推理,默认为Object,在Java 8中推断出最具体的适用类型,在此情况下为String。 JLS for Java 8引入了新章节第18章。



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


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


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



< hr>

如Java Tutoral中的示例所示(


考虑以下方法:

$

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




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



  processStringList(Collections.emptyList( )); 




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




 列表< Object>不能转换为List< String> 




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



  processStringList(类别<字符串>的emptyList()); 




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

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

这种不兼容性在 JDK 8兼容性指南






您可以查看我的答案,找到与重载方法相关的类似问题解决方案 根据 JLS 15.12.2.5选择最具体的方法
$ b


如果多于一个成员方法都可以访问并适用于
方法调用,则必须选择一个提供运行时方法调度的
描述符。 Java编程
语言使用最具体的方法被选择的规则。

然后:


一个适用的方法m1比另一个适用的
方法m2更具体,对于具有参数表达式e1,...,ek的调用,如果
以下任何一个都是真的:



  1. m2是通用的,对于$而言,m1被推断为比m2更具体b $ b参数表达式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是不通用的,m1和m2适用于变量arity
    调用,并且m1
    的前k个变量arity参数类型为S 1,...,Sk和m2
    的前k个变量参数类型为T1,...,Tk,对于所有的i(1 ≤i≤k)。此外,如果m2具有k + 1个参数,则
    第m + 1个变量arity参数类型是m2
    k + 1变量arity参数类型的子类型。

以上条件是唯一一种方法可能比另一种方法更具体。



如果S<:T(§4.10),则对于任何表达式,S类型比T类型更具体。 $ b

三个选项中的第二个与我们的案例匹配。由于 String Object的子类型( 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天全站免登陆