Java 调用看起来模棱两可,但在意外输出下运行良好

Java 调用看起来模棱两可,但在意外输出下运行良好,java,null,Java,Null,请看这个Java类 class Demo { public static void a(String s) { System.out.println("string called"); } public static void a(Object a) { System.out.println("Object called"); } public static void main(String...asrgs)

请看这个Java类

class Demo
{
    public static void a(String s)
    {
        System.out.println("string called");
    }
    public static void a(Object a)
    {
        System.out.println("Object called");
    }
    public static void main(String...asrgs)
    {
        a(null);
    }
}
这段代码的输出是“stringcalled”,但我无法理解编译器是如何在对象和字符串之间解析的

此外,请检查此代码片段

class Demo
{
    public static void a(String s)
    {
        System.out.println("string called");
    }
    public static void a(Integer n)
    {
        System.out.println("number called");
    }
    public static void a(Object a)
    {
        System.out.println("Object called");
    }
    public static void main(String...asrgs)
    {
        a(null);
    }
}
这里我们得到了一个与不明确调用相关的编译时错误(这是很明显的)。 对此有什么好的解释吗?

答案在于JLS的以下部分:

第二步在上一步中确定的类型中搜索成员方法。此步骤使用方法的名称和参数表达式来定位可访问和适用的方法,即可以对给定参数正确调用的声明

这种方法可能不止一种,在这种情况下,选择最具体的方法。最具体方法的描述符(签名加返回类型)是在运行时用于执行方法分派的描述符

(我的重点)

…并且,上面一节提到的,它有专门性规则的全部细节,但也有这个方便的摘要:

非正式的直觉是,如果第一个方法处理的任何调用都可以传递给另一个方法而不会出现编译时错误,那么一个方法比另一个方法更为具体

在第一个示例中,
a(String)
a(Object)
更具体,因此编译器知道使用哪一个,并且很高兴。在第二个示例中,
a(String)
a(Integer)
都比
a(Object)
更具体,但两者都适用于
null
,并且它们是独立的谱系(
String
String>Object
Integer
Integer>Number>Object
),创建编译器抱怨的歧义

如果他们是同一血统,就不会有歧义,因为只有一个适用的最具体的选项。例如:

class Base {
}
class Child extends Base {
}
class GrandChild extends Child {
}
public class Example {

    public static final void main(String[] args) {
        a(null);
    }

    public static void a(Base b) {
        System.out.println("Base");
    }

    public static void a(Child b) {
        System.out.println("Child");
    }

    public static void a(GrandChild b) {
        System.out.println("GrandChild");
    }
}

这会打印出
“孙子”
,因为虽然
a(子)
a(孙子)
a(对象)
更具体,但
a(孙子)
a(子)
对第一个或第二个的解释更具体,如果可能的话:)