我正在尝试使用此代码:
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)
?
答案 0 :(得分:82)
我希望选择方法 考虑到真实(不是 声明)参数类型。我错过了吗 什么?
是。你的期望是错误的。在Java中,动态方法分派仅针对调用方法的对象发生,而不是针对重载方法的参数类型。
引用Java Language Specification:
调用方法时(第15.12节), 实际参数的数量(以及任何 显式类型参数)和 参数的编译时类型 在编译时使用 确定方法的签名 将被调用(§15.12.2)。如果 要调用的方法是 实例方法,实际方法 被调用将在运行时确定 时间,使用动态方法查找 (§15.12.4)。
答案 1 :(得分:79)
如前所述,重载分辨率是在编译时执行的。
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 [])的任何参数,所以 令人困惑(对象)不太具体。 (每个双数组是对象,但不是每个对象都是双数组。)最具体的构造函数因此令人困惑(双[]),这解释了程序的输出。
如果传递类型为 double [] 的值,则此行为有意义;如果你传递 null ,这是违反直觉的。 理解这个难题的关键是对哪个方法或构造函数最具体的测试不使用实际参数:调用中出现的参数。 它们仅用于确定适用的过载。一旦编译器确定哪些重载适用且可访问,它就只使用形式参数选择最具体的重载:声明中出现的参数。
要使用 null 参数调用 Confusing(Object)构造函数,请编写 new 混乱((对象)为null)。这可确保只有 Confusing(Object)适用。更多 通常,要强制编译器选择特定的重载,请将实际参数转换为形式参数的声明类型。
答案 2 :(得分:15)
根据参数类型调度方法的能力称为multiple dispatch。在Java中,这是通过Visitor pattern完成的。
但是,由于您正在处理Integer
和String
,因此无法轻松合并此模式(您无法修改这些类)。因此,对象运行时的巨型switch
将是您的首选武器。
答案 3 :(得分: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 :(得分:4)
我有一个类似的问题,调用一个名为“参数”的类的正确构造函数,它可以采用几种基本的Java类型,如String,Integer,Boolean,Long等。给定一个Object数组,我想转换它们通过为输入数组中的每个Object调用最具体的构造函数,将我的Parameter对象放入一个数组中。我还想定义会抛出IllegalArgumentException的构造函数Parameter(Object o)。我当然发现这个方法是为我的数组中的每个Object调用的。
我使用的解决方案是通过反射查找构造函数...
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语句或访问者模式! :)
答案 5 :(得分:2)
Java在尝试确定要调用的方法时查看引用类型。如果要强制使用代码,请选择“正确”方法,可以将字段声明为特定类型的实例:
Integeri = new Integer(12);
String s = "foobar";
Object o = new Object();
你也可以将你的参数作为参数的类型:
callee.foo(i);
callee.foo((String)s);
callee.foo(((Integer)o);
答案 6 :(得分:0)
如果方法调用中指定的参数的数量和类型与重载方法的方法签名之间存在完全匹配,那么这就是要调用的方法。您正在使用Object引用,因此java在编译时决定对于Object param,有一个直接接受Object的方法。所以它称这种方法为3次。