【问题标题】:Method overloading and choosing the most specific type方法重载和选择最具体的类型
【发布时间】:2012-03-10 19:46:07
【问题描述】:

示例代码为:

    public class OverloadingTest {

       public static void test(Object obj){
           System.out.println("Object called");
       }

       public static void test(String obj){
           System.out.println("String called");
       }

       public static void main(String[] args){
           test(null);
           System.out.println("10%2==0 is "+(10%2==0));
           test((10%2==0)?null:new Object());
           test((10%2==0)?null:null);
   }

输出是:

调用的字符串
10%2==0 为真
对象调用
字符串调用

第一次调用test(null) 调用带有String 参数的方法,根据The Java Language Specification 可以理解。

1) 谁能解释我在前面的调用中调用test() 的依据是什么?

2) 当我们再次输入 if 条件时:

    if(10%2==0){
        test(null);
    }
    else
    {
        test(new Object());
    }

它总是调用带有String 参数的方法。

编译器会在编译时计算表达式(10%2) 吗?我想知道表达式是在编译时还是运行时计算的。谢谢。

【问题讨论】:

  • test((10%2==0)?null:null; 最后一行需要关闭 ) 并且main方法没有关闭。

标签: java static-methods overloading


【解决方案1】:

Java 使用早期绑定。在编译时选择最具体的方法。最具体的方法是根据参数的数量和参数的类型来选择的。在这种情况下,参数的数量无关紧要。这给我们留下了参数的类型。

参数有什么类型?两个参数都是表达式,使用三元条件运算符。问题归结为:条件三元运算符返回什么类型?类型在编译时计算。

给出两个表达式:

(10%2==0)? null : new Object(); // A
(10%2==0)? null : null; // B

类型评估规则列于here。在B 中很简单,两个术语完全相同:null 将被返回 (whatever type that may be) (JLS: "如果第二个和第三个操作数的类型相同(可能是 null 类型),那么那是条件表达式的类型。”)。在A 中,第二个术语来自特定的类。因为这是更具体的,null 可以替换为 Object 类的对象,所以整个表达式的类型是 Object(JLS:“如果第二个和第三个操作数之一是空类型并且类型另一个是引用类型,那么条件表达式的类型就是那个引用类型。”)。

在表达式的类型评估之后,方法选择符合预期。

您给出的if 示例不同:您使用两种不同 类型的对象调用方法。三元条件运算符总是在编译时被评估为一个类型,同时满足这两个条件。

【讨论】:

  • 但是条件呢?一点都不重要吗?在这两种情况下条件都为真;并根据三元运算符规则选择 null 作为答案。
  • 是的,但这与整个表达式的 type 问题无关。特定的result 在运行时评估,而term 的类型 在编译时评估。当然,结果的类型可能会有所不同,具体取决于所选术语的作用。所以条件无关紧要,因为它不用于类型评估,仅用于结果评估。
  • 这一点尤其明显,因为条件也可能是一个表达式,只能在运行时进行评估。作为一个理论上的例子:new Random().nextBoolean().
  • 我怎样才能调用最不具体的方法??
【解决方案2】:

JLS 15.25:

条件表达式的类型确定如下:

[...]

  • 如果第二个和第三个操作数之一为空类型,而另一个为空类型 是引用类型,那么条件表达式的类型就是那个引用 输入。

[...]

所以类型

10 % 2 == 0 ? null : new Object();

是对象。

【讨论】:

  • ... 所以 (10%2==0)?null:new Object() 的类型是 Object。
【解决方案3】:
test((10%2==0)?null:new Object());

等同于:

Object o;

if(10%2==0)
    o=null;
else
    o=new Object();

test(o);

由于o 的类型是Object(就像(10%2==0)?null:new Object() 的类型一样)test(Object) 将始终被调用。 o 的值无关紧要。

【讨论】:

    【解决方案4】:

    您的答案是:运行时,因为在运行时指定参数是否是 String 的实例,所以在编译时找不到这个。

    【讨论】:

      【解决方案5】:

      这是一个非常好的问题。

      让我试着澄清一下你上面写的代码。

      • 在您的第一个方法调用中

      测试(空);

      在此null 将被转换为字符串类型,因此调用test(String obj),根据 JLS,您确信该调用。

      • 在第二个方法调用中

      test((10%2==0)?null:new Object());

      这将返回布尔“真”值。所以第一个布尔“真”值将自动转换为布尔包装类对象。布尔包装对象正在寻找与三元运算符中的 new Object() 选项的最佳匹配。并且该方法以Object作为参数调用,因此它调用了以下方法

      公共静态无效测试(对象 obj)

      为了实验起见,您可以尝试以下组合,然后您将获得更好的清晰度。

      test((10 % 2 == 0) ? new Object() : "stringObj" );

      test((10 % 2 == 0) ? new Object() : null );

      test((10 % 2 == 0) ? "stringObj" : null );

      • 最后在你用下面的代码调用的时候。

      测试((10%2==0)?null:null);

      这一次它再次返回布尔“真”值,并且将再次遵循上述相同的强制转换。但是这次你的三元运算符中没有new Object() 参数。所以它将自动类型转换为null 对象。它再次遵循与您的第一个方法调用相同的方法调用。

      • 最后当你询问代码时,如果你输入if .. else 语句。然后编译器也会对代码做出公平的决定。

      如果(10%2==0){ 测试(空); }

      在这里,您的 if 条件始终为真并调用此代码 test(null)。因此,它始终以 String 作为参数调用 firsttest(String obj) 方法,如上所述。

      【讨论】:

        【解决方案6】:

        我认为你的问题是你做出了错误的假设,你的表达方式:

        test((10%2==0)?null:new Object());
        

        test((10%2==0)?null:null;
        

        总是会调用test(null),这就是为什么他们会通过test(Object)。

        【讨论】:

        • 我认为您错过了他的观点,并且您自己做出了错误的假设。第二个调用 test(Object) 而是调用 test(String)。
        【解决方案7】:

        正如@Banthar 提到的,?: 运算符首先为变量赋值,然后评估条件。 另一方面,您提到的if 条件总是返回true,因此编译器将仅用if 的主体替换整个if-else 块。

        【讨论】:

          【解决方案8】:

          1)test()方法是由编译时参数的类型决定的:

          test((Object) null);
          test((Object)"String");
          

          输出:

          Object called
          Object called
          

          2) 编译器更加智能,编译后的代码就相当于:

          test(null);
          

          你可以用javap -c检查字节码:

             0: aconst_null   
             1: invokestatic  #6                  // Method test:(Ljava/lang/String;)V
             4: return  
          

          【讨论】:

            【解决方案9】:

            这就是Java Language Specifications 对问题的看法。

            如果有多个方法声明既可访问又适用 对于方法调用,有必要选择一个来提供 运行时方法分派的描述符。 Java 编程 语言使用选择最具体方法的规则。

            这是你的例子中的 test(String) 方法。

            因此,如果您添加...

            public static void test(Integer obj){
                       System.out.println("Ingeter called");
                   }
            

            它会显示编译错误 - 方法 test(String) 对于类型 OverloadingTest 不明确。

            正如 JLS 所说:

            可能没有最具体的方法,因为有 两种或更多种最具体的方法。在这种情况下:

            如果所有最大特定方法都具有相同的签名,则: 如果没有将最具体的方法之一声明为抽象的,则它 是最具体的方法。否则,所有最大特定 方法必须声明为抽象的。最具体的方法是 在最具体的方法中任意选择。然而 如果和 仅当在每个的 throws 子句中声明该异常时 最具体的方法。否则,我们说该方法 调用不明确,出现编译时错误。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-05-13
              相关资源
              最近更新 更多