"Amigo, do you like whales?"

"Whales? Nope, never heard of them."

"It's like a cow, only bigger and it swims. Incidentally, whales came from cows. Uh, or at least they share a common ancestor. It doesn't matter."

Polymorphism and overriding - 1

"Listen up. I want to tell you about another very powerful tool of OOP: polymorphism. It has four features."

1) Method overriding.

Imagine that you've written a "Cow" class for a game. It has lots of member variables and methods. Objects of this class can do various things: walk, eat, sleep. Cows also ring a bell when they walk. Let's say you've implemented everything in the class down to the smallest detail.

Polymorphism and overriding - 2

Then suddenly the customer says he wants to release a new level of the game, where all actions take place in the sea, and the main character is a whale.

You started to design the Whale class and realize that it's only slightly different than the Cow class. Both classes use very similar logic, and you decide to use inheritance.

The Cow class is ideally suited to be the parent class: it already has all the necessary variables and methods. All you need to do is add the whale's ability to swim. But there's a problem: your whale has legs, horns, and a bell. After all, the Cow class implements this functionality. What can you do?

Polymorphism and overriding - 3

Method overriding comes to the rescue. If we inherit a method that does not do exactly what we need in our new class, we can replace the method with another one.

Polymorphism and overriding - 4

How is this done? In our descendant class, we declare the method that we want to change (with the same method signature as in the parent class). Then we write new code for the method. That's it. It's as if the parent class's old method doesn't exist.

Here's how it works:

Code Description
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
Here we define two classes: Cow and WhaleWhale inherits Cow.

The Whale class overrides the printName(); method.

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
This code displays «I'm a cow» on the screen.
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
This code displays «I'm a whale» on the screen

After it inherits Cow and overrides printName, the Whale class actually has the following data and methods:

Code Description
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
We know nothing about any old method.

"Honestly, that's what I was expecting."

2) But that's not all.

"Suppose the Cow class has a printAll, method that calls the two other methods. Then the code would work like this:"

The screen will show:
I'm white
I'm a whale

Code Description
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
The screen will show:
I'm white
I'm a whale

Note that when the Cow class's printAll () method is called on a Whale object, the Whale's printName() method will be used, not the Cow's.

The important thing is not the class the method is written in, but rather type (class) of the object on which the method is called.

"I see."

"You can only inherit and override non-static methods. Static methods are not inherited and therefore cannot be overridden."

Here's what the Whale class looks like after we apply inheritance and override the methods:

Code Description
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Here's what the Whale class looks like after we apply inheritance and override the method. We know nothing about any old printName method.

3) Type casting.

Here's an even more interesting point. Because a class inherits all the methods and data of its parent class, an object of this class can be referenced by variables of the parent class (and the parent of the parent, etc., right up to the Object class). Consider this example:

Code Description
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
The screen will show:
I'm white.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
The screen will show:
I'm white.
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
The screen will show:
Whale@da435a.
The toString() method is inherited from the Object class.

"Good stuff. But why would you need this?"

"It's a valuable feature. You'll understand later that it is very, very valuable."

4) Late binding (dynamic dispatch).

Here's what it looks like:

Code Description
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
The screen will show:
I'm a whale.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
The screen will show:
I'm a whale.

Note that it is not the type of the variable that determines which specific printName method we call (that of the Cow or the Whale class), but rather the type of object referenced by the variable.

The Cow variable stores a reference to a Whale object, and the printName method defined in the Whale class will be called.

"Well, they didn't add that for the sake of clarity."

"Yeah, it's not that obvious. Remember this important rule:"

The set of methods you can call on a variable is determined by the variable's type. But which specific method/implementation gets called is determined by the type/class of the object referenced by the variable.

"I'll try."

"You'll run into this constantly, so you'll quickly understand it and never forget."

5) Type casting.

Casting works differently for reference types, i.e. classes, than it does for primitive types. However, widening and narrowing conversions also apply to reference types. Consider this example:

Widening conversion Description
Cow cow = new Whale();

A classic widening conversion. Now you can only call methods defined in the Cow class on the Whale object.

The compiler will let you use the cow variable only to call those methods defined by the Cow type.

Narrowing conversion Description
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
A classic narrowing conversion with a type check. The cow variable of type Cow stores a reference to a Whale object.
We check that this is the case, and then perform the (widening) type conversion. This is also called type casting.
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
You can also perform a narrowing conversion of a reference type without type-checking the object.
In this case, if the cow variable is pointing at something other than a Whale object, an exception (InvalidClassCastException) will be thrown.

6) And now for something tasty. Calling the original method.

Sometimes when overriding an inherited method you don't want to entirely replace it. Sometimes you just want to add a little bit to it.

In this case, you really want the new method's code to call the same method, but on the base class. And Java let's you do this. This is how it's done: super.method().

Here are some examples:

Code Description
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
The screen will show:
I'm white
This is false: I'm a cow
I'm a whale

"Hmm. Well, that was some lesson. My robot ears almost melted."

"Yes, this isn't simple stuff. It's some of the most difficult material you'll encounter. The professor promised to provide links to materials from other authors, so that if you still don't understand something, you can fill in the gaps."