根据参数的实际types重载方法select

我正在试验这个代码:

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)三次。 我希望方法的select考虑到真实(而不是声明的)参数types。 我错过了什么吗? 有没有办法修改这个代码,以便它打印foo(12)foo("foobar")foo(Object o)

我希望方法的select考虑到真实(而不是声明的)参数types。 我错过了什么吗?

是。 你的期望是错误的。 在Java中,dynamic方法调度只发生在调用方法的对象上,而不是用于重载方法的参数types。

引用Java语言规范 :

当一个方法被调用时(§15.12),实际参数(和任何显式types参数)的数量和参数的编译时间types在编译时被用来确定将被调用的方法的签名§15.12.2)。 如果要调用的方法是实例方法,则将使用dynamic方法查找(第15.12.4节)在运行时确定要调用的实际方法。

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

Java Puzzlers有一个很好的例子:

益智46:混乱的build设者案例

这个难题给你两个混乱的构造函数。 主要方法调用一个构造函数,但哪一个? 程序的输出取决于答案。 该程序打印什么,甚至是合法的?

 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); } } 

解决scheme46:混淆构造函数的情况

Java的重载parsing过程分两个阶段进行。 第一阶段select所有可访问和适用的方法或构造函数。 第二阶段select第一阶段中select的最具体的方法或构造函数。 如果一个方法或构造函数可以接受任何传递给另一个方法的参数[JLS 15.12.2.5],那么这个方法或构造函数就不是那么具体

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

如果你传递一个double []types的值,这种行为是有道理的。 如果您传递null,这是违反直觉的。 理解这个难题的关键是,哪个方法或构造函数最具体的testing不使用实际的参数 :参数出现在调用中。 它们仅用于确定哪些重载适用。 一旦编译器确定哪些重载是适用的和可访问的,它将仅使用forms参数来select最具体的重载:参数出现在声明中。

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

能够调用基于参数types的方法调用称为多派遣 。 在Java中,这是通过访问者模式完成的。

但是,由于你正在处理IntegerString ,所以你不能轻易地将这个模式合并(你不能修改这些类)。 因此,物体运行时间的巨大switch将是您select的武器。

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

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

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

如果你有很多types,这是不可pipe理的,那么方法重载可能不是正确的方法,而公共方法应该只是采取对象和实现某种策略模式委托每个对象types适当的处理。

我有一个类似的问题,调用一个叫做“Parameter”的类的正确的构造函数,它可能需要几个基本的Javatypes,比如String,Integer,Boolean,Long等等。给定一个对象数组,我想把它们转换成一个数组通过调用input数组中每个对象的最具体构造函数来调用我的Parameter对象。 我也想定义将引发IllegalArgumentException的构造函数Parameter(Object o)。 我当然发现这个方法被调用我的数组中的每个对象。

我使用的解决scheme是通过reflection查找构造函数…

 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语句或访问者模式需要! 🙂

Java在试图确定要调用的方法时会查看引用types。 如果你想强制你的代码select“正确的”方法,你可以声明你的字段为特定types的实例:

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

你也可以把你的参数作为参数的types:

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

如果在方法调用中指定的参数的数量和types与重载方法的方法签名之间存在完全匹配,那么这将是将被调用的方法。 你正在使用Object引用,所以java在编译时决定,对于Object param,有一个直接接受Object的方法。 所以它调用了这个方法3次。