CodeGym /Courses /Java Core /Polymorphism and overriding

Polymorphism and overriding

Java Core
Level 2 , Lesson 1
Available

"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."

Comments (47)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Thomas Level 13, Scottsdale, United States
11 August 2024
GREAT THREAD ! If this wasn't a wacky example no one would read it !! Dear Codegym - this Cow Class to Whale transformation is a great hilarious lesson and very thought provoking ... but it's a bit long with all these topics linked up ) lol -- great graphics on method overriding / super / casting / late binding / .. inheritance etc
Evgeniia Shabaeva Level 42, Budapest, Hungary
1 July 2024
So, just to recap. Polymorphism has 4 (four) features: 1) Method overriding. 2) That's not all. 3) Type casting. 4) Late binding. 5) Type casting. 6) Calling the original method. How come? Dear CodeGym, this lesson was very informative, and I'm grateful for it, but could you please do something about its structure?
Sqwyz Level 29, France, France
10 June 2025
😂🤣
Bwambale Doweens Level 109, Uganda Expert
8 May 2023
The explanation is just great and gives you all the details of the big picture.
Hoist Level 38, San Diego, United States
28 October 2022
These comment forums get wayyy more valuable as Java gets increasingly advanced --- it differentiates Codegym from so many other education formats. And the Videos are pretty decent too, even the ones that kind of just wing it ! >> Awards to all the people here whom take the time to debate and share.
Mary Khan Level 23, Russia, Russian Federation
16 May 2022
You list 4 features of polymorphism and one of them is named : 2) But that's not all. Really?
DarthGizka Level 24, Wittenberg, Germany
2 June 2021
For a course that is supposed to be *teaching* programming this is clearly beyond the pale. Inheritance for the sake of reusing bits of something fundamentally different is a lazy, sloppy programmer's cop-out and it leads to incomprehensible, unmaintainable spaghetti programs. Inheritance hierarchies are first and foremost about conceptual modelling (i.e. about program *understandability* and structuring), not about saving two or three keystrokes. The only proper way to go in every respect is to factor out the commonality into an interface or base class - say Mammal - from which Cow and Whale can derive. The proper, proper way in a complicated project might even be to make a common interface for the interface commonality (modelling aspect, nothing to do with implementation) and a base class for re-usable bits of logic/code (which is an implementation detail that should be independent from the public modelling hierarchy). Modern IDEs can help you a lot with such refactoring. Public inheritance means that there is an 'is-a' relationship between base and derived class*; there is no such relationship between Cow and Whale. It would be as wrong to say 'a Cow is a Whale' as it is to say 'a Whale is a Cow' - which is what the code clump above has modelled. From the point of view of the compiler a Whale 'is-a' Cow - it lets you use Whale instances in every place where where Cow instances can be used. *) 'base and derived class' are meant in the generic sense; this includes interface and implementing class in structurally challenged languages like Java, C# and Delphi
Naughtless Level 41, Olympus Mons, Mars
3 June 2021
I agree with you, but I think the course is just trying to explain method overriding in a easily digestible way. CodeGym also has a habit of explaining something in a way that is not necessarily the best practice (even bad practice sometimes), but then explains the proper way to use the knowledge after we have a clear grasp of the concept as a whole in future lessons.
DarthGizka Level 24, Wittenberg, Germany
3 June 2021
I agree with your assessment. However, if they present bad code for the sake of shooting it down later then it should be clearly marked as 'bad code, do not do this (you'll learn why shortly)'. Otherwise most first sightings of things - and thus first memories - would be of *bad* examples that should not be emulated.
ImDevin Level 15, Old Town, United States
23 May 2021
Am I mistaken? or is the type casting backwards here? Isn't it widening (not narrowing), if you go from a class(Whale) that extends the parent class(Cow)? Cow cow = new Whale();
Naughtless Level 41, Olympus Mons, Mars
3 June 2021
I have the same question...
Anonymous #10756622 Level 13, United States of America
13 September 2021
Same question. Could someone help?
Jonaskinny Level 25, Redondo Beach, United States
24 February 2022
Don't think in terms of lager class (sub) being widing, and smaller class (super) as being narrowing. Think of it in terms of what will always work (fit) and what would need a specific cast. Everything (accept primatives and a few other internals) in java is an Object, inherits from Obejct, and will fit in an Object. Any Object, that is a subtype of Object, would need to be safely cast (narrowing) to see if it fits in that subtype of Object. String is an Object. if you KNOW its a string, you could (unwise) do (String)Object without first checking its instance type (instanceof), but if it's not a String you get ClassCastException. String can always be cast to Object ie (Object)String. Think of it in terms of wide or narrow populations. 1 superclass, 3 subclasses, 3 objects, one of each subclass. That gives you three objects of type superclass (wider population) and 1 of each subtype (narrow population). So if you just guess, you have a 1/3 chance of getting it right going from larger population (super) to fit it into the correct smaller population (sub) for any given object. cleaner explaination vs CG
Gellert Varga Level 23, Szekesfehervar, Hungary
18 January 2021
/* Comment has been deleted */
Roman Level 66
27 January 2021
Static methods in Java are inherited but cannot be overridden. If you declare the same method in a subclass, you hide the superclass's method instead of overriding it.
Gellert Varga Level 23, Szekesfehervar, Hungary
29 January 2021
Thanks! But "to hide" or "to override" - is it not the same effect? Is there any difference between them?
Roman Level 66
1 February 2021
https://docs.oracle.com/javase/tutorial/java/IandI/override.html
Gellert Varga Level 23, Szekesfehervar, Hungary
2 February 2021
Thank You! This article is very useful. It is clear now.
31 December 2020
The variable type determines the method you can use. The specific method implementation depends on the type of object being used.
Agent Smith Level 38
24 August 2020
This CG article explains narrowing/widening references better - Widening and narrowing of reference types 24-AUG-2020: Also, narrowing and widening in this lesson seems to be in the wrong order!