Say I have a class animal, and classes cat and dog which extend animal. Cat, animal and dog all have a method speak() which for cat prints "meow" and for dog prints "woof" and for animal "can't speak".
Alright so finally here's my question. What exactly happens if a make a cat (c
) and then run Animal a = c;
? What happens if I run a.speak();
? Which speak()
method is called? What exactly has happened when I change types like that? Will I ever have any real reason to use this?
Objects in Java know exactly what type they were created as; it's effectively a hidden field (which you can retrieve with the Object.getClass()
method). Moreover, all non-static method resolution starts with the method definitions of the most specific class and proceeds towards the most generic class (Object
); since there is only ever single inheritance of implementation in Java, this is a simple search. Cat
knows it's a subtype of Animal
, which is a subtype of Object
, and c
knows that it's a Cat
independent of what type the variable is.
When you do the assignment, the compiler checks if the known type of the value being assigned is the type being assigned to or one of its subtypes. If it is, the assignment works. If it isn't, you'll need an explicit cast (which puts in a proper type-check at runtime; casts can't break Java's type-system, they can just make it ugly). It doesn't change the fact that method lookup is still done dynamically and the object still knows what type it is really; all the program is doing is neglecting some information.
If you know C++, then think of Java as having only virtual methods (and static methods) and it's very simple to handle the lookups into the vtables because there's no problem with inheritance diamonds or other evil cases.
When working with implementations of an interface, it's almost the same except that a more complex lookup is done (i.e., first the index in the vtable is looked up before proceeding as before). However, to have an object that implements the interface means that there must be some class that fully implements the interface, by which point it's all relatively simple again. And remember, all the really complicated stuff is done at compilation time; at run time, things are relatively straight-forward in all cases.
So, will you make use of any of this? Well, you should (and you'll actually find it very hard to avoid in real code). It's good style to think in terms of having a contract defined by an interface or superclass, where the subtypes obey the contract without the caller having to know about the details. Java libraries use this very heavily, particularly since the visibility of the type details of the contract and its fulfillment can be different. All the client code knows is that the object obeys the contract, that it is of the given type.