Method overloading and choosing the most specific

2019-01-01 16:07发布

问题:

The sample code is :

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

And the output is :

String called
10%2==0 is true
Object called
String called

The first call to test(null) invokes the method with String argument , which is understandable according to The Java Language Specification .

1) Can anyone explain me on what basis test() is invoked in preceding calls ?

2) Again when we put , say a if condition :

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

It always invokes the method with String argument .

Will the compiler compute the expression (10%2) while compiling ? I want to know whether expressions are computed at compile time or run time . Thanks.

回答1:

Java uses early binding. The most specific method is chosen at compile time. The most specific method is chosen by number of parameters and type of parameters. Number of parameters is not relevant in this case. This leaves us with the type of parameters.

What type do the parameters have? Both parameters are expressions, using the ternary conditional operator. The question reduces to: What type does the conditional ternary operator return? The type is computed at compile time.

Given are the two expressions:

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

The rules of type evaluation are listed here. In B it is easy, both terms are exactly the same: null will be returned (whatever type that may be) (JLS: \"If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.\"). In A the second term is from a specific class. As this is more specific and null can be substituted for an object of class Object the type of the whole expression is Object (JLS: \"If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.\").

After the type evaluation of the expressions the method selection is as expected.

The example with if you give is different: You call the methods with objects of two different types. The ternary conditional operator always is evaluated to one type at compile time that fits both terms.



回答2:

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

Is the same as:

Object o;

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

test(o);

Since type of o is Object (just like the type of (10%2==0)?null:new Object()) test(Object) will be always called. The value of o doesn\'t matter.



回答3:

JLS 15.25:

The type of a conditional expression is determined as follows:

[...]

  • If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.

[...]

So the type of

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

is Object.



回答4:

Your answer is : Runtime because in runtime specify parameter is instance of String or not so in compile-time can\'t find this.



回答5:

This is the really nice question.

Let me try to clarify your code that you have written above.

  • In your first method call

test(null);

In this the null will be converted into string type so calling the test(String obj), as per JLS you are convinced with the call.

  • In the second method call

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

Which is going to return the boolean \"true\" value. So first boolean \"true\" value is going to auto cast into Boolean Wrapper class object. Boolean wrapper Object is finding the best match with your new Object() option in the ternary operator. And the method calls with Object as a parameter so it calls the following method

public static void test(Object obj)

For the experiment sake you can try the following combinations then you will get better clarity.

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

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

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

  • Finally in the last when you are calling with the following code.

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

This time again it returns as boolean \"true\" value, and it will again follow the same casts as explained above. But this time there is no new Object() parameter is there in your ternary operator. So it will be auto type cast into null Object. Again it follows same method call as the your first method call.

  • In the last when you asked for code if you put in if .. else statement. Then also the compiler doing the fair decision with the code.

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

Here all the time your if condition is true and calling this code test(null). Therefore all the time it call the firsttest(String obj) method with String as parameter as explained above.



回答6:

I think your problem is that you are making the wrong assumption, your expressions:

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

and

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

Will always call test(null), and that\'s why they will go through test (Object).



回答7:

as @Banthar mentionend the ?: operator assigns a value to a variable first then evaluates the condition. On the other hand, the if condition you mentioned always returns true, so the compiler will replace the whole if-else block with only the body of the if.



回答8:

1) the test() method is determined by the type of the parameter at the compilation time :

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

output :

Object called
Object called

2) The compiler is even smarter, the compiled code is equivalent to just :

test(null);

you can check the bytecode with javap -c:

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


回答9:

This is what Java Language Specifications say about the problem.

If more than one method declaration is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

This is test(String) method in your case.

And because of that if you add...

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

it will show compilation error -The method test(String) is ambiguous for the type OverloadingTest.

Just like JLS says:

It is possible that no method is the most specific, because there are two or more maximally specific methods. In this case:

If all the maximally specific methods have the same signature, then: If one of the maximally specific methods is not declared abstract, it is the most specific method. Otherwise, all the maximally specific methods are necessarily declared abstract. The most specific method is chosen arbitrarily among the maximally specific methods. However, the most specific method is considered to throw a checked exception if and only if that exception is declared in the throws clauses of each of the maximally specific methods. Otherwise, we say that the method invocation is ambiguous, and a compile-time error occurs.