为什么Java8泛型类型推断选择这个重载?

为什么Java8泛型类型推断选择这个重载?,java,generics,java-8,language-lawyer,Java,Generics,Java 8,Language Lawyer,考虑以下计划: 公共类泛型推理{ 公共静态void main(字符串[]args){ 打印(新的SillyGenericWrapper().get()); } 私有静态无效打印(对象){ System.out.println(“对象”); } 私有静态无效打印(字符串){ System.out.println(“字符串”); } 公共静态类SillyGenericWrapper{ 公共部门得不到{ 返回null; } } } 它在Java8下打印“字符串”,在Java7下打印“对象” 我本以为

考虑以下计划:

公共类泛型推理{
公共静态void main(字符串[]args){
打印(新的SillyGenericWrapper().get());
}
私有静态无效打印(对象){
System.out.println(“对象”);
}
私有静态无效打印(字符串){
System.out.println(“字符串”);
}
公共静态类SillyGenericWrapper{
公共部门得不到{
返回null;
}
}
}
它在Java8下打印“字符串”,在Java7下打印“对象”

我本以为这在Java8中是不明确的,因为两个重载方法都匹配。为什么编译器在之后选择打印(字符串)

不管是否合理,这会破坏向后兼容性,并且在编译时无法检测到更改。在升级到Java8之后,代码的行为就不一样了

注意:
SillyGenericWrapper
之所以命名为“傻”是有原因的。我试图理解为什么编译器的行为如此,不要告诉我愚蠢的包装是一个糟糕的设计


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

我使用Java1.8.0\u40运行它,得到了“Object”

如果要运行以下代码:

public class GenericTypeInference {

private static final String fmt = "%24s: %s%n";
public static void main(String[] args) {

    print(new SillyGenericWrapper().get());

    Method[] allMethods = SillyGenericWrapper.class.getDeclaredMethods();
    for (Method m : allMethods) {
        System.out.format("%s%n", m.toGenericString());
        System.out.format(fmt, "ReturnType", m.getReturnType());
        System.out.format(fmt, "GenericReturnType", m.getGenericReturnType());   
   }

   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;
       }
   }
}
公共类泛型推理{
私有静态最终字符串fmt=“%24s:%s%n”;
公共静态void main(字符串[]args){
打印(新的SillyGenericWrapper().get());
方法[]allMethods=SillyGenericWrapper.class.getDeclaredMethods();
对于(方法m:所有方法){
System.out.format(“%s%n”,m.toGenericString());
System.out.format(fmt,“ReturnType”,m.getReturnType());
格式(fmt,“GenericReturnType”,m.getGenericReturnType());
}
私有静态无效打印(对象){
System.out.println(“对象”);
}
私有静态无效打印(字符串){
System.out.println(“字符串”);
}
公共静态类SillyGenericWrapper{
公共部门得不到{
返回null;
}
}
}
您将看到您得到:

对象公共T com.xxx.GenericTypeInference$SillyGenericWrapper.get() ReturnType:类java.lang.Object GenericReturnType:T


这解释了为什么使用对象重载的方法而不是字符串重载的方法。

首先,它与重写无关,但必须处理重载

Jls.提供了大量有关编译器如何准确选择重载方法的信息

在编译时选择最具体的方法;它的描述符 确定在运行时实际执行的方法

所以当调用

print(new SillyGenericWrapper().get());
编译器选择
String
version而不是
Object
,因为采用
String
print
方法比采用
Object
的方法更具体。如果存在
Integer
而不是
String
,则会选择它

此外,如果要调用将
对象
作为参数的方法,则可以将返回值分配给
对象
类型的参数,例如

public class GenericTypeInference {

    public static void main(String[] args) {
        final SillyGenericWrapper sillyGenericWrapper = new SillyGenericWrapper();
        final Object o = sillyGenericWrapper.get();
        print(o);
        print(sillyGenericWrapper.get());
    }

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

    private static void print(Integer integer) {
        System.out.println("Integer");
    }

    public static class SillyGenericWrapper {
        public <T> T get() {
            return null;
        }
    }
}
当假设你有两个有效的方法定义可以重载时,情况开始变得有趣起来

private static void print(Integer integer) {
    System.out.println("Integer");
}

private static void print(String integer) {
    System.out.println("String");
}
现在如果你调用

print(sillyGenericWrapper.get());

编译器将有两个有效的方法定义可供选择,因此您将得到编译错误,因为它不能优先选择一个方法。

类型推断规则在Java 8中得到了重大改进;最显著的是目标类型推断得到了很大改进。因此,在Java 8之前,方法rgument站点没有收到任何推断,默认为Object,在Java 8中推断出最具体的适用类型,在本例中为String。Java 8的JLS引入了Java 7的JLS中缺少的新章节

JDK1.8的早期版本(直到1.8.0_25)在编译器成功编译代码时出现了与重载方法解析相关的错误,根据JLS,正如Marco13在评论中指出的那样,该代码应该会产生歧义错误

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

这解释了JDK1.8早期版本中的错误,以及您看到的兼容性问题


如Java教程()中的示例所示

考虑以下方法:

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

列表无法转换为列表
编译器需要类型参数T的值,因此它以值对象开始。因此,调用Collections.emptyList返回类型List的值,该值与方法processStringList不兼容。因此,在Java SE 7中,必须按如下方式指定类型参数的值:

processStringList(Collections.emptyList());
这在Java SE 8中不再必要。什么是目标类型的概念已扩展为包括方法参数,例如方法processStringList的参数。在这种情况下,processStringList需要类型为List的参数

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

这种不相容性值得一提


您可以查看我关于重载方法解析的类似问题的答案

根据:

如果超过1米
print(sillyGenericWrapper.get());
void processStringList(List<String> stringList) {
    // process stringList
}
processStringList(Collections.emptyList());
List<Object> cannot be converted to List<String>
processStringList(Collections.<String>emptyList());
    print( (Object)new SillyGenericWrapper().get() );