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


115

我正在尝试以下代码:

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)

Answers:


96

我希望方法选择要考虑实际的(而不是声明的)参数类型。我想念什么吗?

是。您的期望是错误的。在Java中,动态方法分派仅发生在调用该方法的对象上,而不发生在重载方法的参数类型上。

引用Java语言规范

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


4
您能否解释一下您引用的规格。这两个句子似乎相互矛盾。上面的示例使用实例方法,但是显然在运行时未确定要调用的方法。
亚历克斯·沃登

15
@Alex Worden:在这种情况下,方法参数的编译时间类型用于确定要调用的方法的签名foo(Object)。在运行时,考虑到它可能是覆盖该方法的已声明类型的子类的实例,在此方法上调用对象的类确定该方法的哪种实现。
Michael Borgwardt'5

86

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

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)的具体性较低。(每个double数组都是一个Object,但并不是每个Object都是double数组。)因此,最具体的构造函数是Confusing(double []),它说明了程序的输出。

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

要使用参数调用Confusing(Object)构造函数,请编写新的Confusing((Object)null)。这样可以确保仅Confusing(Object)适用。更一般而言,要强制编译器选择特定的重载,请将实际参数转换为形式参数的声明类型。


4
我希望现在还不晚说-“关于SOF的最佳解释之一”。谢谢:)
TheLostMind 2014年

5
我相信,如果我们还添加构造函数'private Confusing(int [] iArray)',它将无法编译,不是吗?因为现在有两个构造函数具有相同的特异性。
Risser 2014年

如果我使用动态返回类型作为函数输入,则始终使用不太具体的方法...说该方法可用于所有可能的返回值...
kaiser

16

基于参数类型调度对方法的调用的能力称为多重调度。在Java中,这是通过Visitor模式完成的。

但是,由于要处理Integers和Strings,因此无法轻松合并此模式(您无法修改这些类)。因此,switch选择对象运行时的巨人将是您的选择武器。


11

在Java中,要调用的方法(使用哪种方法签名)是在编译时确定的,因此它与编译时类型一起使用。

解决此问题的典型模式是使用Object签名检查方法中的对象类型,并使用强制类型转换委托给该方法。

    public void foo(Object o) {
        if (o instanceof String) foo((String) o);
        if (o instanceof Integer) foo((Integer) o);
        logger.debug("foo(Object o)");
    }

如果您有很多类型并且无法管理,则方法重载可能不是正确的方法,而公共方法应该仅采用Object并实现某种策略模式来委派每种对象类型的适当处理。


4

在调用名为“ Parameter”的类的正确构造函数时,我遇到了类似的问题,该类可以采用几种基本的Java类型,例如String,Integer,Boolean,Long等。给定对象数组,我想将它们转换为数组通过为输入数组中的每个对象调用最特定的构造函数来确定我的Parameter对象。我还想定义构造函数Parameter(Object o),该构造函数将抛出IllegalArgumentException。我当然发现我的数组中的每个对象都调用了此方法。

我使用的解决方案是通过反射查找构造函数。

public Parameter[] convertObjectsToParameters(Object[] objArray) {
    Parameter[] paramArray = new Parameter[objArray.length];
    int i = 0;
    for (Object obj : objArray) {
        try {
            Constructor<Parameter> cons = Parameter.class.getConstructor(obj.getClass());
            paramArray[i++] = cons.newInstance(obj);
        } catch (Exception e) {
            throw new IllegalArgumentException("This method can't handle objects of type: " + obj.getClass(), e);
        }
    }
    return paramArray;
}

无需难看的instanceof,switch语句或访问者模式!:)


2

Java在尝试确定要调用的方法时会查看引用类型。如果要强制执行代码,请选择“正确”方法,可以将字段声明为特定类型的实例:

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

您还可以将参数转换为参数的类型:

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

1

如果在方法调用中指定的参数数量和类型与重载方法的方法签名之间存在完全匹配,则将调用该方法。您正在使用对象引用,因此java会在编译时确定对于对象参数,有一种直接接受对象的方法。因此它调用了该方法3次。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.