【问题标题】:Overloaded method selection based on the parameter's real type基于参数真实类型的重载方法选择
【发布时间】:2010-12-07 00:54:10
【问题描述】:

我正在试验这段代码:

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)

【问题讨论】:

    标签: java oop


    【解决方案1】:

    我希望方法选择采取 考虑到真实的(不是 声明)参数类型。我失踪了吗 什么?

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

    引用Java Language Specification

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

    【讨论】:

    • 你能解释一下你引用的规格吗?这两句话似乎互相矛盾。上面的例子使用了实例方法,但是被调用的方法显然不是在运行时确定的。
    • @Alex Worden:方法参数的编译时类型用于确定要调用的方法的签名,在本例中为foo(Object)。在运行时,调用该方法的对象的类确定调用该方法的哪个实现,考虑到它可能是覆盖该方法的声明类型的子类的实例。
    【解决方案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) 不太具体。 (每个 double 数组 都是一个 Object,但不是每个 Object 都是 double 数组。)最具体的构造函数因此是Confusing(double[]),它解释了程序的输出。

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

    要使用 null 参数调用 Confusing(Object) 构造函数,请编写 new 令人困惑的((Object)null)。这确保只有 Confusing(Object) 是适用的。更多的 通常,为了强制编译器选择特定的重载,将实际参数强制转换为形式参数的声明类型。

    【讨论】:

    • 我希望现在说“关于 SOF 的最佳解释之一”还为时不晚。谢谢:)
    • 我相信如果我们还添加了构造函数 'private Confusing(int[] iArray)' 它将无法编译,不是吗?因为现在有两个具有相同特异性的构造函数。
    • 如果我使用动态返回类型作为函数输入,它总是使用不太具体的...表示可用于所有可能返回值的方法...
    【解决方案3】:

    根据参数类型分派对方法的调用的能力称为multiple dispatch。在 Java 中,这是通过 Visitor pattern 完成的。

    但是,由于您处理的是Integers 和Strings,因此您不能轻易地合并这种模式(您只是不能修改这些类)。因此,对象运行时的巨型 switch 将是您的首选武器。

    【讨论】:

      【解决方案4】:

      在 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 并实现某种策略模式来委派每个对象类型的适当处理。

      【讨论】:

        【解决方案5】:

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

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

        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 语句或访问者模式! :)

        【讨论】:

          【解决方案6】:

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

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

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

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

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-05-13
              • 2012-12-30
              • 1970-01-01
              • 1970-01-01
              • 2016-09-20
              相关资源
              最近更新 更多