开发者

Is there any reason that Java uses late/static binding for overloaded methods in the same class?

开发者 https://www.devze.com 2023-01-19 06:39 出处:网络
Is there any specific reason why Java uses early binding for overloaded m开发者_Go百科ethods? Wouldn\'t it be possible to use late binding for this?

Is there any specific reason why Java uses early binding for overloaded m开发者_Go百科ethods? Wouldn't it be possible to use late binding for this?

Example:

public class SomeClass {

    public void doSomething(Integer i) {
        System.out.println("INTEGER");
    }

    public void doSomething(Object o) {
        System.out.println("OBJECT");
    }

    public static void main (String[] args) {
        Object i = new Integer(2);
        Object o = new Object(); 
        SomeClass sc = new SomeClass();
        sc.doSomething(i);
        sc.doSomething(o); 
    } 
}

Prints: OBJECT OBJECT

I would rather expect: INTEGER OBJECT


Korgen, I agree with you that such feature could potentially be quite useful, which is why I found this question very interesting.

To be clear; When executing this snippet of code:

Object i = new Integer(2);
SomeClass sc = new SomeClass();
sc.doSomething(i);

the JVM would be supposed to see that i is of runtime type Integer and execute the code as if it had executed the following:

Integer i = new Integer(2);         // Notice the different static type.
SomeClass sc = new SomeClass();
sc.doSomething(i);

(Note that I'm not going into how this should be done internally, I'm just pinning down the hypothetical semantics desired in this particular situation.)

Background: (Korgen, you can skip this) Selecting which function to call is sometimes referred to as "dispatch". When taking a single type into account (the type of o in o.m()) when deciding which function to call, it is called single dispatch. What we're after here is called multiple dispatch since we would decide which function to call based on multiple types (i.e. both the runtime type of the callee and the runtime type of the arguments).

Possible reason for not including support for multiple dispatch in Java:

  1. Efficiency. Single dispatch can be much more efficiently implemented by means of a virtual method table. To quote the Wikipedia article on Double dispatch:

    In a language supporting double dispatch, this is slightly more costly, because the compiler must generate code to calculate the method's offset in the method table at runtime, thereby increasing the overall instruction path length.

  2. Legacy. This is the behavior in languages that have inspired Java, such as C++, Smalltalk and Eiffel. At the very least, single dispatch follows the principle of least astonishment.

  3. Complexity. The specification on how to determine which method to call is a quite interesting read. It is amazingly complex, and how to push the complexity over from the compiler to the JVM is by no means obvious. Consider for example the following snippet:

    class A {                          .--------------.
        void foo(Object o) {}          |      A       |
    }                                  '--------------'
                                              |
    class B extends A {                .--------------.
        void foo(Integer i) {}         |      B       |
    }                                  '--------------'
                                              |
    class C extends B {                .--------------.
        void foo(Number n) {}          |      C       |
    }                                  '--------------'
    

    now which method should be called here:

    A c = new C();
    Object i = new Integer(0);
    c.foo(i);
    

    According to the runtime type of the callee C.foo should be called while according to the runtime type of the argument B.foo should be called.

    One option would be to resolve this the same way as a call staticMethod(c, i) would be resolved in the presence of staticMethod(A, Object), staticMethod(B, Integer) and staticMethod(C, Number). (Note however that in this case neighter B.foo nor C.foo would be called, as suggested above.)

    Another option would be to choose the method primarily based on the type of the callee and secondarily based on the types of the arguments, in which case C.foo would be called.

    I'm not saying that it is impossible to pin down a well defined semantics, but I it would arguably make the rules even more complex, and possibly even counter-intuitive in some aspects. In the case of early binding, at least the compiler and/or IDE can aid the developer by giving guarantees of what will actually happen in runtime.


It seems to me that the most obvious reason is that it allows the compiler to guarantee that there will actually be a function to be called.

Suppose Java chose the function based on the run-time type, and you wrote this:

public class MyClass
{
  public void foo(Integer i)
  {
    System.out.println("Integer");
  }
  public void foo(String s)
  {
    System.out.println("String");
  }
  public static void main(String[] args)
  {
    Object o1=new String("Hello world");
    foo(o1);
    Object o2=new Double(42);
    foo(o2);
  }
}

What's the output? The first call to foo presumably prints "String", but the second call has nowhere to go. I suppose it could generate a run-time error. This is similar to the argument of strictly-typed versus loosely-typed. If it chose the function at run time, it could be more flexible in some sense. But by choosing the function at compile time, we get the error messages at compile time rather than having to wait until run time and be sure that we have exercised every possible path with every relevant combination of data.


It is very simple. The method to use is chosen by the compiler and not the runtime system. This is what allows the type checking in the compiler to work in the first place.

So, if you stuff an Integer in an Object you must tell the compiler that you KNOW it contains an Integer, so the appropriate method can be chosen.

What you want to accomplish is normally done with methods on the object, so that "this.doSomething()" does what you want it to do.


It is actually late binding, not early binding. Early binding only happens for non-overrideable methods.

Given this code:

public class Test
{
        void foo()
        {
                System.out.println("foo");
        }

        final void bar()
        {
                System.out.println("bar");
        }

        void car(String s)
        {
                System.out.println("car String");
        }

        void car(Object o)
        {
                System.out.println("car Object");
        }

        static void star()
        {
                System.out.println("star");
        }

        public static void main(final String[] argv)
        {
                Test test;
                Object a;
                Object b;

                test = new Test();
                a    = "Hello";
                b    = new Object();
                test.foo();
                test.bar();
                test.car(a);
                test.car(b);
                Test.star();
        }
}

The javac I used generates this for main:

public static void main(java.lang.String[]);
  Code:
   0:   new #9; //class Test
   3:   dup
   4:   invokespecial   #10; //Method "<init>":()V
   7:   astore_1
   8:   ldc #11; //String Hello
   10:  astore_2
   11:  new #12; //class java/lang/Object
   14:  dup
   15:  invokespecial   #1; //Method java/lang/Object."<init>":()V
   18:  astore_3
   19:  aload_1
   20:  invokevirtual   #13; //Method foo:()V
   23:  aload_1
   24:  invokevirtual   #14; //Method bar:()V
   27:  aload_1
   28:  aload_2
   29:  invokevirtual   #15; //Method car:(Ljava/lang/Object;)V
   32:  aload_1
   33:  aload_3
   34:  invokevirtual   #15; //Method car:(Ljava/lang/Object;)V
   37:  invokestatic    #16; //Method star:()V
   40:  return    
}

invokevirtual means late binding, invokestatic and invokespecial means early binding.

The line:

24: invokevirtual #14; //Method bar:()V

refers to a non-overrideable method, so logically speaking it should be invokespecial. The runtime is apparently free to make the change when the class is loaded (I could be wrong on that, I haven't delved that deeply into the VM internals, but from what I read it seems that is the case).

So your question is really why doesn't java have what is called Multiple Dispatch (wikipedia link here) which is where the runtime decides what method to call based on the value in the variable instead of basing it on what the variable is declared as.

The way the compiler works is to say something like:

  • I am calling SomeClass.doSomething on a variable declared to be an Object.
  • Does SomeClass have a method called doSomething that takes an Object?
  • If yes, then output an invokevirtual call to that method.

What you are wanting is an additional step that happens at runtime (it could not happen at compile time) that says:

  • The variable is pointing at an Integer.
  • I am calling the SomeClass.doSomething method.
  • Call the best match of SomeClass.doSomething method that takes an Integer, a Number, an Object (call whichever it finds first).

Java doesn't to that at runtime, instead it simply calls the method that the compiler decided to call.

You can simulate multiple dispatch in Java like so.


Is there any specific reason why Java uses early binding for overloaded methods? Wouldn't it be possible to use late binding for this?

One problem with dynamic binding of overloaded methods is that it wouldn't work if the different overloads had different return types. The runtime behavior would be harder to understand, and the application might have to deal with a new class of runtime exceptions caused by dynamic overload resolution failing.

A second problem is that selecting the method to use dynamically based on the actual argument types would be expensive. You cannot implement this by simple vtable method dispatching.

Besides, this is unnecessary since you can get dynamic binding of methods by using method overriding / polymorphism.


It's possible. And even more, there is such code in standard library(class - TreeSet, author (sic!) Josh Bloch ).

In one of his lecture he says that it was mistake.

From Joshua Bloch How to Design a Good API & Why it Matters

Overload With Care

  • Avoid ambiguous overloadings
    • Multiple overloadings applicable to same actuals
    • Conservative: no two with same number of args
  • Just because you can doesn't mean you should
    • Often better to use a different name
  • If you must provide ambiguous overloadings, ensure same behavior for same arguments

    public TreeSet(Collection c); // Ignores order

    public TreeSet(SortedSet s); // Respects order


void doSomething(Comparable c) {..}
void doSomething(Iterable i) {..}

class Foo implements Comparable, Iterable { ..}

doSomething(new Foo()); // which one??


You see OBJECT OBJECT rather than INTEGER OBJECT because you've declared i to be an Object, not an Integer. If you do this instead:

public class SomeClass {

    public void doSomething(Integer i) {
        System.out.println("INTEGER");
    }

    public void doSomething(Object o) {
        System.out.println("OBJECT");
    }

    public static void main (String[] args) {
        Integer i = new Integer(2);
        Object o = new Object(); 
        SomeClass sc = new SomeClass();
        sc.doSomething(i);
        sc.doSomething(o); 
    } 
}

You'll get INTEGER OBJECT.

http://ideone.com/sEZrP


As Thorbjørn's answer explains, this is because the method call is disambiguated at compile time, not at run time.


Other people have explained the "why" better than I could.

What I will say, though, is that if you want that behaviour, you'll want to take a look at Double Dispatch, and particularly the Visitor Pattern.

0

精彩评论

暂无评论...
验证码 换一张
取 消