基于参数实数类型的重载方法选择

2022-08-31 08:34:41

我正在试验这段代码:

interface Callee {
    public void foo(Object o);
    public void foo(String s);
    public void foo(Integer i);
}

class CalleeImpl implements Callee
    public void foo(Object o) {
        logger.debug("foo(Object o)");
    }

    public void foo(String s) {
        logger.debug("foo(\"" + s + "\")");
    }

    public void foo(Integer i) {
        logger.debug("foo(" + i + ")");
    }
}

Callee callee = new CalleeImpl();

Object i = new Integer(12);
Object s = "foobar";
Object o = new Object();

callee.foo(i);
callee.foo(s);
callee.foo(o);

这打印了三次。我希望方法选择考虑真正的(不是声明的)参数类型。我错过了什么吗?有没有办法修改此代码,以便它将打印 ,和 ?foo(Object o)foo(12)foo("foobar")foo(Object o)


答案 1

我希望方法选择考虑真正的(不是声明的)参数类型。我错过了什么吗?

是的。你的期望是错误的。在 Java 中,动态方法分派仅针对调用该方法的对象进行,而不针对重载方法的参数类型。

引用 Java 语言规范

调用方法时 (§15.12),在编译时将使用实际参数(以及任何显式类型参数)的数量和参数的编译时类型来确定将被调用的方法的签名 (§15.12.2)。如果要调用的方法是实例方法,则将在运行时使用动态方法查找 (§15.12.4) 确定要调用的实际方法。


答案 2

如前所述,重载解析是在编译时执行的。

Java Puzzlers有一个很好的例子:

谜题 46:令人困惑的构造函数的案例

这个谜题为您提供了两个令人困惑的构造函数。main 方法调用构造函数,但哪个构造函数呢?程序的输出取决于答案。程序打印什么,或者它是否合法?

public class Confusing {

    private Confusing(Object o) {
        System.out.println("Object");
    }

    private Confusing(double[] dArray) {
        System.out.println("double array");
    }

    public static void main(String[] args) {
        new Confusing(null);
    }
}

解决方案 46:令人困惑的构造函数的情况

...Java的重载解决过程分两个阶段运行。第一阶段选择所有可访问且适用的方法或构造函数。第二阶段选择在第一阶段中选择的最具体的方法或构造函数。如果一个方法或构造函数可以接受传递给另一个方法或构造函数的任何参数,则它不如另一个方法或构造函数那么具体 [JLS 15.12.2.5]。

在我们的程序中,这两个构造函数都是可访问和适用的。构造函数 Confusing(Object) 接受传递给 Confusing(double[]) 的任何参数,因此 Confusing(Object) 不太具体。(每个双精度数组都是一个对象,但不是每个对象都是双精度数组。因此,最具体的构造函数是 Confusing(double[]),它解释了程序的输出。

如果传递 double[] 类型的值,则此行为是有意义的;如果传递 null,则违反直觉。理解这个难题的关键是,对于哪个方法或构造函数最具体的测试不使用实际的参数:出现在调用中的参数。它们仅用于确定哪些重载是适用的。一旦编译器确定哪些重载是适用和可访问的,它就会选择最具体的重载,仅使用形式参数:出现在声明中的参数。

若要使用 null 参数调用 Confusing(Object) 构造函数,请编写新的 Confusing((Object)null)。这确保了只有混淆(对象)是适用的。更一般地说,要强制编译器选择特定的重载,将实际参数强制转换为声明类型的形式参数。