0

i am trying to understand dynamic/static binding on a deeper lever and i can say after a lot of reading and searching i got really confused about something.

Well, java uses dynamic binding for overriden methods and the reason for this is that the compiler doesn't know to which class the method belongs to, right? For example :

public class Animal{
       void eat(){
}

class Dog extends Animal{
       @Override
       void eat(){}
}

public static void main(String[] args[]){
     Dog d = new Dog();
     d.eat();
}

My question is why the compiler doesn't know that the code refers to the Dog class eat() method, even though d reference is declared to be of class Dog and Dog's constructor is used to create the instance at runtime? The object is going to be created at runtime, but why the compiler doesn't understand that the code refers to Dog's method?Is it a matter of the compiler's design or am i missing something?

Rogue
  • 11,105
  • 5
  • 45
  • 71
  • 1
    It doesn't know because it doesn't care. The point of polymorphism is that the developer shouldn't need to know what the implementation called with actually be, `javac` does almost no optimisations, it just validates your code. – Peter Lawrey Nov 04 '16 at 19:30

3 Answers3

3

and the reason for this is that the compiler doesn't know to which class the method belongs to, right?

Actually, no. The compiler doesn't want to know the specific type of the target object. This allows code compiled now to work in the future with classes that don't even exist yet.

As the most obvious example consider a JDK method like Collections.sort(List). You can pass it an implementation of List that you just created. You don't want to have to notify Oracle that you did it, and hope they include it in their list of "statically supported" list types.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • Thanks for your time.What do you actually mean by "statically supported"? –  Nov 04 '16 at 12:29
  • Assuming no dynamic dispatch, the compiler would have to know about your type. That means that Oracle would have to know about it at compile time. That's what I meant. – Marko Topolnik Nov 04 '16 at 12:30
  • But, if we assume i pass my own List implementation object, i must import the source code, so again, compiler will know even with new classes, right? –  Nov 04 '16 at 12:50
  • 1
    No, you don't need the source code at all. You can compile your `List` implementation without ever hearing about `Collections.sort`, and `Collections.sort` can be compiled without knowing about your code. Then, in a third round, someone compiles code which uses the two precompiled JARs, one with `Collections.sort`, the other with your `List`, and makes use of them. – Marko Topolnik Nov 04 '16 at 12:53
  • 1
    And note that you don't even need those JARs with compiled methods, all you need is _declarations_ of those classes and method signatures. – Marko Topolnik Nov 04 '16 at 13:02
  • That is what i am saying too, in the third round you mentioned, the compiler is, in theory at least, able to know that in Collections.sort(myListObj), myListObj is of type MyList, because i have already imported it. So why it would be a problem to bind even in a situation like this? –  Nov 04 '16 at 13:04
  • 1
    But don't forget that, in your picture, the _implementation_ of `sort` must know about your type. You have no control over, or even no _awareness_ of the implementation, but once you pass it your object, it works by dynamic dispatch. – Marko Topolnik Nov 04 '16 at 13:06
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/127379/discussion-between-nikos-and-marko-topolnik). –  Nov 04 '16 at 13:12
  • The big problem of this discussion is that is is entirely unclear what the claim “[the compiler is] designed to not be able to distinguish in which class eat belongs” is supposed to mean. What does it not know? To which consequences? Actually, the compiler knows very well, that `Dog` has a declaration of `eat()`. So what is the OP actually missing? Which different behavior should the compiler exhibit in the OP’s opinion? – Holger Nov 04 '16 at 16:21
3

Dynamic binding is absolutely necessary. For example, let's say you have something like this:

Animal a;
String kind = askTheUser();
if (kind.equals("Dog") {
    a = new Dog();
}
else {
    a = new Cat();
}
a.eat();

Clearly here, the compiler can't know, at compile-time, that a is a Dog. It could be a Cat. So it has to use dynamic binding.

Now you could say that in your example, the compiler could know and could optimize. That's not however how Java has been designed. Most of the optimizations happen at runtime, thanks to the JIT compiler. The JIT compiler is (probably) able to make this optimization at runtime, and much more, that a static compiler would not be able to do. Java thus decided to make the static compiler and the byte-code simpler, and to concentrate its optimization efforts in the JIT compiler.

So when the compiler compiles this, it just cares about the d.eat() line only. d is of type Dog, eat() is an overridable method that exists in the Dog class hierarchy, and the byte-code used to invoke this method dynamically is the generated.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
2

It’s not clear on what your question is actually founded.

When you have code of the form

 Dog d = new Dog();
 d.eat();

the static type of d is Dog and hence, the compiler will encode an invocation of Dog.eat() into the class file, after checking that the invocation is correct.

For the invocation, there are several scenarios possible

  • Dog might declare a method eat() that overrides a method with the same signature in its superclass Animal, like in your example
  • Dog might declare a method eat() that does not override another method
  • Dog might not declare a matching method, but inherit a matching method from its superclass or implemented interfaces

Note that it is completely irrelevant, which scenario applies. If the invocation is valid, it will get compiled to an invocation of Dog.eat(), regardless of which case applied, because the formal static type of d, on which eat() is invoked, is Dog.

Being that agnostic to the actual scenario also implies that at runtime, you might have a different version of the class Dog, to which another scenario applies, without breaking the compatibility.


It would be a different picture if you had written

Animal a = new Dog();
a.eat();

Now the formal type of a is Animal and the compiler will check whether Animal contains a declaration for eat(), be it overridden in Dog or not. This invocation will then be coded as targeting Animal.eat() in the byte code, even though the compiler could deduce that a is actually a reference to a Dog instance. The compiler just follows the formal rules. This implies that this code would not work, if the runtime version of Animal lacked an eat() method, even if Dog has one.


This implies that it would be a dangerous change to remove a method in a base class, but you can always refactor your code adding a more abstract base class and move methods up the class hierarchy, without affecting the compatibility with existing code. This was one of the goal of the Java designers.

So perhaps, you compiled one of the two example above and later, you’re running your code with a newer library version, in which the type hierarchy is Animal>Carnivore>Dog and Dog hasn’t an implementation of eat(), because the natural place for the most specific implementation is Carnivore.eat(). In that environment, your old code will still run and do the right thing, without problems.

Further note that even if you recompile your old code without changes, but using the newer library, it will stay compatible with the old library version, as in your code, you never refer to the new Carnivore type and the compiler will use the formal types, you use in your code, Animal or Dog, not recording the fact that Dog inherits the method eat() from Carnivore into the compiled code, according to the formal rules as explained above. No surprises here.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • So, dynamic binding is there to provide flexibility with possible changes in the OOP design, correct?If java could be static-bound only, the only issue coming from this would be this incompatibility with future changes in class design? –  Nov 04 '16 at 18:07
  • @Nikos Note: `static` methods are determined at compile time. – Peter Lawrey Nov 04 '16 at 19:31
  • 1
    No, it's not at all just about future changes. It is the basic ability to write polymorphic code which allows you, as an example, to abstract away an algorithm from the details of a memory structure. You write `sort` just once and it works with array-list, linked-list, array-deque, etc. – Marko Topolnik Nov 04 '16 at 20:37
  • @Nikos: It’s not clear what you mean by the term “dynamic binding”. The target method is resolved at compile-time, i.e. if there is the need to make a choice among overloaded methods, that choice is made at compile-time. Besides that, the OOP design of Java demands that the invocation of an instance method will end up at the most specific implementation for the actual runtime class of the object. So if the actual type of your object is `Dog` and `Dog` has an implementation of `eat()`, the invocation of `eat()` on that `Dog` instance will end up at `Dog`’s `eat()` method. That’s not “dynamic”. – Holger Nov 07 '16 at 10:01
  • The other aspect, as I tried to explain in my answer, is, what will be recorded in the class file during the compilation. There, the behavior is not dynamic but rather surprisingly static, i.e. the exact compile-time type of the reference is used to denote the receiver type of a method invocation. These rules exist for compatibility, as adding arbitrary “smartness” into the compiler yielding different compiler results would make compatibility unpredictable. But this receiver type is irrelevant when it comes to the actual runtime behavior, as there, only the actual type of the object matters. – Holger Nov 07 '16 at 10:06
  • @Peter Lawrey: not exactly. When the `static` method is not declared in an interface, the JVM will search the superclass hierarchy, when the target method is not declared in the specified target class. Of course, this search will only done once, during linkage (i.e. the first invocation), as it will never change again within the same runtime. – Holger Nov 07 '16 at 10:11
  • @Holger I though the `javac` compile determined which class a static method is called on at compile time. If you refer to a static method via a sub-class which doesn't implement it, this doesn't appear in the byte code. – Peter Lawrey Nov 07 '16 at 10:41
  • 2
    @Peter Lawrey: nope, the type as written in the source code is used, i.e. `JFrame.getWindows()` gets compiled as `invokestatic javax/swing/JFrame.getWindows:()[Ljava/awt/Window;`, not `invokestatic java/awt/Window.getWindows:()[Ljava/awt/Window;`. I just verified it, to ensure my memory is correct. The reason is as explained in my answer, no surprises regarding references to classes that are never used on source code level. For the new feature of `static` methods in interfaces, the rules are different, i.e. you aren’t even allowed to refer to them via subclass in source code. – Holger Nov 07 '16 at 10:50
  • 1
    @Holger thank you for the correction. Again you have taught me something new. – Peter Lawrey Nov 07 '16 at 11:02