简体   繁体   中英

Java Static and Dynamic Binding, Overloading

I am practicing for a test and I came across this exercise about overloading and static and dynamic binding. The output of the following code is asked:

class Moe {
    public void print(Moe p) {
        System.out.println("Moe 1");
    }
}

class Larry extends Moe {
    public void print(Moe p) {
        System.out.println("Larry 1");
    }
    public void print(Larry l) {
        System.out.println("Larry 2");
    }
}

class Curly extends Larry {
    public void print(Moe p) {
        System.out.println("Curly 1");
    }
    public void print(Larry l) {
        System.out.println("Curly 2");
    }
    public void print(Curly b) {
        System.out.println("Curly 3");
    }
}

class Overloading {
    public static void main (String [] args) {
        Larry stooge1 = new Curly();
        Moe stooge2 = new Larry();
        Moe stooge3 = new Curly();
        Curly stooge4 = new Curly();
        Larry stooge5 = new Larry();

        stooge1.print(new Moe());
        stooge1.print(new Curly());
        stooge1.print(new Larry());
        stooge2.print(new Curly());
        stooge3.print(new Curly());
        stooge3.print(new Larry());
        stooge5.print(new Curly());
    }
}

I think I get the first one but on the others I am completely lost. This is how I solved the first one:

at runtime the type of stooge1 is Curly , so we're calling the print method of Curly. Because we pass an object of type Moe to print, the corresponding print method with argument type Moe is run in Curly . The output of this method is Curly 1 , the correct answer.

However, when I apply this technique to the following lines I end up with the wrong answers. Can someone explain me how exactly this concept works in Java?

The correct output of the code is:

Curly 1
Curly 2
Curly 2
Larry 1
Curly 1
Curly 1
Larry 2

Static binding happens at compilation time and dynamic binding at runtime.

  • Static binding is responsible for selecting signature (name and argument types) of method which should be executed. It uses

    • name of method
    • type of variables holding arguments (compiler doesn't assume what actual object variable will hold at runtime, he picks signature which will be able to handle all of possible cases).

    Compiler selects signature from variable type on which method is invoked , so Object o = "abc"; will not allow you to invoke o.substring(1,2); because compiler will not be able to find substring(int, int) signature in Object class (which is type of o variable on which substring method was invoked).

  • Dynamic binding is responsible for finding and invoking code of method selected by static binding at compilation time. It will try to find code of method in type of actual instance held by variable. In other words if you have Animal a = new Cat(); a.makeSound(); Animal a = new Cat(); a.makeSound(); you can expect to get as result "Mew" because at runtime JVM will search and invoke code of makeSound starting from Cat class. If implementation will not be provided in that class JVM will search for it in ancestor(s) until it finds one from which it was inherited.


I renamed classes and variables in your example a little to hopefully make it more readable:

class A {
    public void print(A a) {
        System.out.println("A.print(A)");
    }
}

class B extends A {
    public void print(A a) {
        System.out.println("B.print(A)");
    }
    public void print(B b) {
        System.out.println("B.print(B)");
    }
}

class C extends B {
    public void print(A a) {
        System.out.println("C.print(A)");
    }
    public void print(B b) {
        System.out.println("C.print(B)");
    }
    public void print(C c) {
        System.out.println("C.print(C)");
    }
}

class OverloadingDemo {
    public static void main (String [] args) {
        A ab = new B();
        A ac = new C();
        B bb = new B();
        B bc = new C();

        bc.print(new A());
        bc.print(new C());
        bc.print(new B());
        ab.print(new C());
        ac.print(new C());
        ac.print(new B());
        bb.print(new C());
    }
}

(variable naming -> variable of type X holding instance of type Y is named xy ).

So, when we execute

bc.print(new A());
  • static binding will try to find best print method signature available in class B which can handle instance of type A . In this case it will be print(A) .
  • after that dynamic binding will search for code of this method in class C (since this is type of instance held by bc variable) which means we will see C.print(A) .

Similarly in case of bc.print(new C());

  • static binding will try to find best print method for C argument available in B class, which for C is print(B) (since there is no print(C) there and B is closest supertype).
  • So now dynamic binding knows which method to look for in C class (since this is instance which bc holds).

So it will invoke C.print(B) .

Here is what's going on:

stooge1.print(new Moe()); // All three have overload for Moe,
// so the overload from the dynamic type of stooge1 gets called
stooge1.print(new Curly()); // Compiler thinks stooge1 is Larry,
// so it does not know that it has an overload for Curly.
// It uses the overload for Larry instead, because Curly is a Larry
stooge1.print(new Larry()); // Same logic as above applies.
stooge2.print(new Curly()); // Compiler thinks stooge2 is Moe, so its only overload
// is for Moe. Since the dynamic type is Larry, first overload is invoked

The remaining three cases can be solved by applying the same logic as above.

For #1, #2, #3 stooge1 is declared a Larry , so only methods available to Larry can be called.

Passing a Moe will call print(Moe) . Since the actual class is a Curly , it prints "Curly 1".

Passing a Larry will call print(Larry) , since that is a better match that print(Moe) . This will print "Curly 2".

Passing a Curly will also call print(Larry) . Note that print(Curly) is unknown to stooge1 , so it cannot be selected by the compiler. Therefore it also prints "Curly 2".

Now try to figure out the rest.

So this is a super confusing and awful example of something you should never do. The declared type of the variables matters for what signature methods have. So Larry doesn't have method that accepts a Curly , so the compiler considers the argument a Larry . But it gets dispatched to Curly 's version of the method.

So yeah, never ever do this =\\

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM