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

Ser*_*nov 107 java oop

我正在尝试这段代码:

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);
Run Code Online (Sandbox Code Playgroud)

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

Mic*_*rdt 91

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

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

引用Java语言规范:

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

  • @Alex Worden:方法参数**的编译时类型**用于确定要调用的方法的签名,在本例中为`foo(Object)`.在运行时,调用该方法的**对象的类确定调用该方法的哪个实现,同时考虑到它可能是覆盖该方法的声明类型的子类的实例. (15认同)
  • 你能解释一下你引用的规格吗?这两句话似乎相互矛盾.上面的示例使用实例方法,但是在运行时显然没有确定被调用的方法. (4认同)

den*_*nov 83

如前所述,重载分辨率是在编译时执行的.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

  • 我相信如果我们还添加了构造函数'private Confusing(int [] iArray)'它将无法编译,不是吗?因为现在有两个具有相同特异性的构造函数. (5认同)
  • 我希望现在说太晚了 - "对SOF最好的解释之一".谢谢 :) (4认同)
  • +1"最具体" - >我需要知道的全部:) (2认同)

Ant*_*lev 15

能够基于参数类型调度方法的调用称为多调度.在Java中,这是通过访问者模式完成的.

但是,由于您正在处理Integers和Strings,因此无法轻松地合并此模式(您无法修改这些类).因此,switch物体运行时的巨人将成为您的首选武器.


Yis*_*hai 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)");
    }
Run Code Online (Sandbox Code Playgroud)

如果你有很多类型并且这是无法管理的,那么方法重载可能不是正确的方法,而是公共方法应该只使用Object并实现某种策略模式来委托每个对象类型的适当处理.