Inheritance and Polymorphism
You have seen this video earlier, but we ignored the inheritance and polymorphism sections of it.
Watch them now (starting from 3 minutes into the video – but you might want to watch again from the start to revise the earlier concepts!)
First, a quick recap of classes and OOP
Why do we need classes and OOP?

First of all, how annoying would it be to represent cartoon animals without classes (at all!!)?
There are so many problems with the code above that we aren’t even going to bother tweaking it!
Say hello to OOP classes
By making a new class of type Animal, we can do something as follows:
Isn’t that a lot more elegant?
Get your hands dirty!
Before we continue with introducing inheritance, edit the above Repl:
- First of all, this code is not gender inclusive!
booleanis not a suitable type for gender. So instead, change the code as follows:- Inside the
Animalclass, define the followingenumtype:public enum Gender {FEMALE, MALE, NON_BINARY} - Remove the use of the
boolean isFemaleeverywhere, and replace it withGender gender. - In the
main()method, assign values for the gender. You will write the values asAnimal.Gender.FEMALEetc instead oftrue/false. - Print the
genderin theintroduce()method, or use itsgenderto determine the corresponding title (e.g.,"Mr","Ms", or"Mx"). Using aswitchcase statement is a nice way to go about it.
- Inside the
- Now implement the
walk()method in theAnimalclass, with a series ofif-elsestatements (or aswitchcase statement) to check thetype:if (type.equals("Rabbit")) { System.out.println("I'm a Rabbit, so I hop on my 2 legs!"); } else if (type.equals("Cat")) { System.out.println("I'm a Cat, so I stroll along!"); } else { System.out.println("Don't know how a " + type + " walks!"); } - Wow – that code above looks bad!
- Call the
walk()method on theAnimalinstances we have in themain()method. - Mistype one of the animal types (e.g. make
lolaaRabitt). The compiler didn’t seem to mind! - We can begin to appreciate that some
Animals have the same functionality (e.g. the way theyintroduce()themselves), but that we might also want a more elegant way to differentiate between different types ofAnimals.
Inheritance
Say hello to OOP inheritance
In the recap above, the Get your hands dirty! exercise should have made you appreciate that the Animal class is not perfect.
Inheritance is a way to address this.
In its simplest form, inheritance allows us to extend an existing class.

This will allow us to reuse the logic in the other class (i.e. we inherit from it). The Cat and Rabbit classes will each extend the Animal class, and inherit the non-private methods and non-private fields.
The class we inherit from is commonly called one of the following: parent class, super class, or base class. This is really just different names meaning the same thing. The Animal class above is an example of a super-class.
The extending class is commonly called one of the following: child class, sub class, or derived class. Again, different names meaning the same thing. The Cat and Rabbit classes above are examples of sub-classes.
We think of inheritance as defining an “is a” relationship:
- A
Rabbit“is a”Animal. - A
Cat“is a”Animal. - A
Student“is a”Person. - A
Teacher“is a”Person.
Understanding the super keyword
Before we have a look at example code, let’s first talk about a new keyword that is often used with inheritance: super.
As we said above, the “super” class (also known as parent or base class) is the class that we extend.
So, the super keyword simply means “the class we are extending”. We can use this keyword while inside a sub class (also known as child or derived class).
Another way to think of super is to relate it to what we already understand regarding this:
- We can think of
thisas being synonymous with “me” or “my”. - We can therefore think of
superas being synonymous with “my parent”.
Now, back to our example! We can implement it with the following code:
Notice the following improvements compared to when we only had the Animal class:
- We no longer need a
typevariable – the class itself (i.e.CatandRabbit) is the type! - We no longer need to specify the number of legs (in the
main()method when constructing the instances). ACatwill have 2 legs and aRabbitwill have 4 legs.
Notice also how the Cat and Rabbit classes are pretty much empty! All they have is a constructor that calls what appears to be a super() method. This is actually calling the Animal constructor! If we said super is synonymous with “my parent”, then super() means “call my parent’s constructor”. This is important, because:
- In constructing a
Cat(child class), we need to first construct anAnimal(parent class). - In constructing a
Rabbit(child class), we need to first construct anAnimal(parent class).
Default (super) constructors
Remember how the compiler will automatically insert a default constructor for a class you have defined (assuming you don’t provide another constructor)?
Well, the compiler also automatically tries to call the default super constructor if you don’t call a super constructor explicitly yourself! The default call to a super constructor is to call the zero-parameter super constructor (i.e. super()). In other words, the immediate parent must have a zero-parameter constructor for this to work!
Let’s visualise the code!
Following the same conventions of the “blueprint” analogy we previously used to visualise classes, here’s how we can visualise classes with inheritance. In particular, we visualise the code snippet shown just now.
The parent class (Animal)
First, let’s look at our base/parent class:

There is nothing unusual with this Animal class. But let’s just mention a couple of things:
- We have added icons to denote what fields and methods are declared as:
private, or
public.
- We can also visualise what an
enumdeclaration might look like. We notice this particularenumis:- named
Gender(which names the type), pubilc, and- names three possible values (
FEMALE,MALE, andNON_BINARY).
- named
The child classes (Cat and Rabbit)
Let’s now look at the children classes (Cat and Rabbit) that extend Animal:

The visualisation gives us an appreciation for what it means to say “Cat extends Animal“:
- The
Catblueprint quite literally extends theAnimalblueprint! - There is nothing new added in the
Cat(orRabbit) blueprint sections, other than their respective constructors!- All the other logic is in the
Animalparent blueprint.
- All the other logic is in the
But the real question is, why are some of the sections from the Animal class covered up?! Well, these were declared as private in the Animal class. As such, they will not be inherited to the children classes and may only be accessed from within the Animal class! Only what’s declared as public (and soon we will see protected) will be accessible by the children classes.
Get your hands dirty!
Let’s understand this a little more:
- Add print statements in all three constructors (
Animal,Cat, andRabbit) to make it clear what constructor is getting called and when. Then run your program. If you do this correctly, you should see the super-class constructor getting called before the sub-class constructor!- Hint: In your sub-class constructors, you must keep the
super()call as the first statement otherwise the code won’t compile (since we are currently using Java version 21)†.
- Hint: In your sub-class constructors, you must keep the
- We will continue improving this code below…
† As a side note, Java version 22 introduced an update so that it’s actually allowed for the super() call to be later in the constructor, under certain conditions. But we won’t worry about that for now, and just keep it as the first statement for simplicity.
Overriding inherited methods
I don’t know about you, but I’m really not happy with this method in the Animal class:
public void walk() {
if (getClass() == Rabbit.class ) {
System.out.println("I'm a Rabbit, so I hop on my 2 legs!");
} else if (getClass() == Cat.class) {
System.out.println("I'm a Cat, so I stroll along!");
} else {
System.out.println("Don't know how a " + getClass().getName() + " walks!");
}
}
What happens if we want to add more sub-classes later on? It’s going to look pretty ugly, especially if we need to do a lot of things inside each block!
To fix this, each sub-class will override the walk() method:

Get your hands dirty!
Edit the same code above:
- Remove all of the content from the
Animal’swalk()method, but keep one of the print statements that says “Don’t know how a … walks!”. - Copy-paste the
walk()method signature fromAnimalinto the other sub-classes, and then include only the relevant print statements depending on what sub-class you are in. Notice how now we have customised thewalk()methods for each respective sub-class, and it’s looking a lot cleaner! - It is a good practice to explicitly state that the
walk()method is overriding, by using the@Overrideannotation. - Add another sub-class (e.g.
Dog.java), and add the relevant constructor for that sub-type and also override thewalk()method for it. Then create an instance of that sub-class, and check it prints what you expect. - What happens if you create an instance of an
Animalin themain()method? Does it make sense to be able to create a somewhat abstract animal type? Probably not! Should we prohibit this from happening? Probably! So, later on we’ll introduce the idea of anabstract class!
If you make the changes above, this is what we will ultimately have:

Both Cat and Rabbit have decided to @Override the public void walk() instance method that is defined in the Animal class. This doesn’t mean that the walk() method in Animal ceases to exist, it just means there is a “revised version” of this method specifically catered for Cat (or Rabbit) instances respectively.
In fact, we can still see that there is a public void walk() declared at the Animal class – it is only its implementation that has been revised.
Protected vs private vs public
It’s not only methods that we inherit from super classes—we also inherit fields!
Actually, strictly speaking, we don’t “inherit” them. Rather, it would be more accurate to say we “inherit access to them”.
However, to inherit access to fields and methods from the super-class, the super-class must allow them to be inherited in the first place!
private
fields and methods will not be accessible to sub-classes (or any other class for that matter),protected
fields and methods will be accessible to sub-classes (regardless of what package they are in), or classes part of the same package (but not other classes that are not sub-classes or part of the same package), andpublic
fields and methods will be accessible to sub-classes (and also other classes that are not sub-classes).
Get your hands dirty!
Continue editing the same code above:
- In the sub-classes (e.g. the overridden
walk()method), try to print out the animal’sname. This is currently declared asprivatein theAnimalclass, so access is not inherited by theCat(orRabbit) sub-class. - Change the modifier of the instance fields inside the
Animalclass toprotectedinstead ofprivate. - You should now be able to access these instance fields in the sub-classes and use them.
- Keep the
yearNowstatic field asprivate, as it’s not going to be needed for the sub-classes as it’s only something that theAnimalclass uses to figure out the animal’s age.
If you make the changes above, this is what we will ultimately have:

In the Cat (or Rabbit) class, we are now able to access any fields or methods declared as protected in the Animal class. The yearNow field we decided to keep as private, so it cannot be accessed from the children classes.
So, what’s the difference of protected
versus public
?
- Both are inherited by the children classes, and accessible to the children classes. No differences in this regard from the point of view of the child class.
- The difference comes outside the parent-child class hierarchy:
- With
protected
, these fields and methods cannot be accessed outside the hierarchy (or outside the package). They are effectively “private” for any class that is not a child class of the Animalclass (or for any class not part of the same package if its not a child class), and effectively “public” for any class that is a child (or grandchild) class of theAnimalclass (or part of the same package). - With
public
, these fields and methods can be accessed outside the hierarchy (and outside the same package). They are effectively “public” for any class (regardless of whether it is part of the class hierarchy or same package).
- With
Calling the parent from the child class
Calling a method that is overriden will automatically execute the implementation closest to the child (derived/sub) class.
What happens if we do not want to completely override the parent’s implementation? Or maybe the child class wants to call the parent’s version of an overridden method—and not the child’s version?
To illustrate this, let’s update how animals introduce() themselves:
- Before animals introduce themselves, they will first say hello in their unique language:
- A
Catwill say “Meow”, and - A
Rabbitwill say “Prrrr”.
- A
- We still want the sub-classes to introduce themselves in the generic manner afterwards (“Hello …” etc), and ideally we still want to inherit this generic functionality from the
Animalclass (i.e. without copy-pasting it for each animal sub-class). At the same time, we still want to allow each animal sub-class to “add their own unique touch”. - No surprise, we achieve this with the
superkeyword again! We want to say “I want to call my parent’s version of introduce”:
public class Cat extends Animal {
...
@Override
public void introduce() {
System.out.print("Meow..."); // Cat's own unique touch
super.introduce(); // Now call "my parent's" version of introduce()
}
}
Get your hands dirty!
Continue editing the same code above:
@Overridetheintroduce()methods for the sub-classes, while also reusing theAnimal’sintroduce()method (as in the example above). Do this for all your sub-classes.- Define a
public String getTitledName()instance method in theAnimalclass:- Use a
switchstatement on thegender, returning the animal’s name with the title:Gender.FEMALE: returns"Ms <name>"Gender.MALE: returns"Mr <name>"Gender.NON_BINARY: returns"Mx <name>"
- Use a
@Overridethepublic String getTitledName()method for theCatclass:- Regardless of gender, cats demand respect. So, return
"Royal <name>".
- Regardless of gender, cats demand respect. So, return
- Make use of the
getTitledName()method as part of theAnimal’sintroduce()method.- Notice how we execute the version that is closest to the child class.
If you make the changes above, this is what we will ultimately have:

- There is a public
getTitledName()method defined in theAnimalclass, so the children will inherit this functionality.- The
Catclass is not happy with that implementation, and so it overrides it with its own definition. - The
Rabbitclass is happy with the existing implementation provided in theAnimalclass, and therefore does not override it.
- The
- Both the
CatandRabbitclasses override theintroduce()method, but they still also make use of the originalintroduce()logic defined in theAnimalclass. - Both the
CatandRabbitclasses override thewalk()method, and completely disregard the originalwalk()logic defined in theAnimalclass.
Inheritance calls summary
Here’s an overview of which method gets called in the context of inheritance. What’s important to note, is that the (overriding) method implementation closest to the derived class gets called, regardless of whether we are in the parent or child class. The fact that the method has been overridden (by a child class) implies that its version should be used – otherwise the parent’s version shouldn’t have been overridden!

One Object to rule them all…
Now that we understand inheritance, we can fully comprehend what the Object class is all about!
If you declare a class, but it doesn’t extend any other class, you are implicitly extending Object! This allows you to use the implementations of toString(), equals(), hashCode() etc inherited from the Object class!
If you declare a class, and you extend some other class, then you still (indirectly) extend Object as that class will be extend Object (or one of its super classes will).
In other words, every class type in Java extends Object one way or another! Every class “is a” Object.
Get your hands dirty!
Continue editing the same code above:
@Override the toString() of some of our classes.
- First override it in the
Animalclass, then see what the output is when you print the animal instance. - Then override the
toString()method in theCatclass (notRabbit). Observe the output from both cats and rabbits. - Comment out the
toString()in theAnimalclass. Observe the output from both cats and rabbits.
Abstract classes
So, we felt it was wrong to be able to create an instance of an Animal type from the main() method:
// Some weird animal with 100 legs!??!
Animal whatKindAmI = new Animal("Weird Animal", 1990, 100, Animal.Gender.MALE);
whatKindAmI.walk();
We say it feels wrong because Animal represents a somewhat abstract type. Besides, we have no meaningful implementation for the walk() method of an abstract animal!
We can therefore decide to declare Animal as an abstract class!
Get your hands dirty!
Continue editing the same code above:
- Make the
Animalclassabstract. - The compiler will now prohibit you from creating an instance of
Animal! Nice. - What happens if you comment out the
walk()method from theCatclass? How will cats walk? Looks like it will use the originalwalk()method defined in theAnimalclass. Does this make sense? Maybe we should force all sub-classes ofAnimalto define awalk()method! - In the
Animalclass, remove the body of thewalk()method and replace it with a semi-colon and add theabstractkeyword, i.e:public abstract class Animal { ... public abstract void walk(); } - Since the
Animalclass isabstract, it’s allowed to have such “empty” methods (but these empty methods need to be declared asabstractalso, just like the class is declared asabstract). TheAnimalclass is essentially saying “I’m too abstract to know how an animal walks, so I will rely on sub-classes to define this particular functionality!” - What it means is, we must provide a
walk()implementation of theCatclass (so bring back thewalk()method you commented out above). If you don’t, then you must make theCatanabstract classalso! And if it’s abstract, then you cannot create an instance of it!
If you make the changes above, this is what we will ultimately have for the (now abstract) Animal class:

From the above visualisation, we can appreciate why it’s not possible to create an instance of an Animal. If we did, then what would be the logic for the abstract void walk() method? The method signature is declared, but there is absolutely no body for the method! Not even a pair of {}!
Even though we are no longer allowed to create instances of Animal, we are still allowed to extend the Animal class:

- The
Animalclass remains anabstractclass. - The extending child classes (
CatandRabbit) are not abstract, because they provide an implementation for all ofAnimal’sabstractmethods (namelywalk()). - Even though we can no longer create just a “pure”
Animalinstance, we still end up creating anAnimalinstance in the process of creating aCatinstance. - If a class (e.g.
Insect) were to extend theAnimalclass, and not provide an implementation for thewalk()method, then thatInsectclass will remain anabstractclass.
Generalisation and specialisation

Inheritance allows for both generalisation (“up the hierarchy”) and specialisation (“down the hierarchy”).
Generalisation:
- Factors out commonality (in terms of methods and fields) of the types.
- For example, all
Animalsintroduce()themselves and havelegs. - Abstraction is closely related to generalisation, where we ignore subtle sub-type differences (e.g. all of our
Animalswalk()—albeit differently).
Specialisation:
- Sub-types have their own particular methods and fields.
- For example, only
Rabbits cancarryEasterEgg(Egg egg), whileCats canchaseMouse(Mouse mouse).
Here’s how we might specialise the child classes, by adding methods that don’t exist in the parent class:
Get your hands dirty!
Edit the code above as follows:
- Specialise the
Rabbitclass some more:- Add a
private String favVegetableinstance field, - Add a parameter to the
Rabbitconstructor to set the field, and - Add a getter
getFavVegetable().
- Add a
- Specialise the
Catclass some more:- Add a
private int numLivesRemaininginstance field, - Initialise the field to
9(allCats start off with 9 lives), and - Add a getter
getNumLivesRemaining().
- Add a
- Complete the
Eggclass:- Add a simple constructor requires a
Stringname for the egg being created, then @OverridethetoString()for theEgg, so it prints its name.
- Add a simple constructor requires a
- Complete the
Mouseclass:- The
MouseclassextendstheAnimalclass, - Add a simple constructor, much like the
CatorRabbit, - Implement all the methods that are required (notice that
Animalis anabstract class), - Add a
runAway()instance method for theMouse, which prints the mouse is scared and running away.
- The
- Make instances of these two new classes where they are currently declared, and then:
- Update the
carryEasterEgg()method to:- Take a parameter of type
Egg, - In the current print message, also print the egg being carried.
- Take a parameter of type
- Update the
chaseMouse()method to:- Take a parameter of type
Mouse, - It should print which mouse the
Catis chasing, and - Invoke that mouse’s
runAway()method.
- Take a parameter of type
- Update the
If you make the changes above, this is what we end up with:

- The
Catclass contains a new fieldnumLivesRemainingthat does not exist in theAnimalclass. - The
Catclass contains a new methodgetNumLivesRemaining()that does not exist in theAnimalclass. - The
Catclass contains a new methodchaseMouse()that does not exist in theAnimalclass. - The
Rabbitclass contains a new fieldfavVegetablethat does not exist in theAnimalclass. - The
Rabbitclass contains a new methodgetFavVegetable()that does not exist in theAnimalclass. - The
Rabbitclass contains a new methodcarryEasterEgg()that does not exist in theAnimalclass.
Visualising instances with inheritance
Here’s how we can visualise our instances following the same visual conventions we have been seeing before:

The instances are still represented as pieces of paper.
We can appreciate why we need to first call the parent class’ constructor:
- Since
RabbitandCatinstances are still partiallyAnimalinstances, we see the commonAnimalfields shared by child classes.
Depending on the actual instance type (i.e. Cat or Rabbit), we have the additional instance fields that are unique to that child class.
Polymorphism
Po ■ ly-
- Prefix from Ancient Greek, many or much.
-morph
- Suffix, from Ancient Greek, form or shape.
Inheritance allowed us to inherit features from another class.
Polymorphism allows us to treat all Animals alike at compile-time, but at run-time the behaviour will depend on the actual object type.
public static void main(String[] args) {
Animal lola = new Rabbit("Lola Bunny", 1996, Animal.Gender.FEMALE);
Rabbit bugs = new Rabbit("Bugs Bunny", 1938, Animal.Gender.MALE);
Cat garfield = new Cat("Garfield", 1978, Animal.Gender.MALE);
...
}
Here’s how we can visualise these three different instances:
Animal lola = new Rabbit("Lola Bunny", 1996, Gender.FEMALE);

The lola reference variable:
- Is declared as being type
Animal. - Has a value (instance) assigned to it of type
Rabbit.
This “mismatch” of declared type and value type is perfectly fine, as a Rabbit “is a” Animal.
- We can place any value of an
Animalinside this box – as long as it’s anAnimalor a sub-class ofAnimal. - This means later on, we can put a different kind of
Animalinside thislolabox (e.g. an instance of aCat). - The only methods that are allowed to be invoked on the
lolavariable are methods declared in theAnimalclass (or its super-class, e.g.Object). - Similarly, the only fields that may be accessed externally on the
lolavariable are fields declared in theAnimalclass.- Note: don’t confuse this with fields in general declared in the
Rabbitclass (e.g.favVegetable). They are still accessible once polymorphism takes us into the context of theRabbitclass (e.g. when we execute an overridden method).
- Note: don’t confuse this with fields in general declared in the
- Why can’t we access
Rabbitfunctionality through thelolavariable?lolamight later on refer to an instance of aCat(or some otherAnimalsub-class).
- The
lolareference variable is therefore polymorphic.
Rabbit bugs = new Rabbit("Bugs Bunny", 1938, Gender.MALE);

The bugs reference variable:
- Is declared as being type
Rabbit. - Has a value (instance) assigned to it of type
Rabbit.
The declared type and value type are both referring to the Rabbit class.
- We can only place a value of a type
Rabbitinside this box (or a sub-class ofRabbit). - This means later on, we can put another
Rabbitinside thisbugsbox (based on its declared type). - The only methods that are allowed to be invoked on the
bugsvariable are methods declared in theRabbitclass (or its super-classes, i.e.AnimalandObject).
Cat garfield = new Cat("Garfield", 1978, Gender.MALE);

The garfield reference variable:
- Is declared as being type
Cat. - Has a value (instance) assigned to it of type
Cat.
The declared type and value type are both referring to the Cat class.
- We can only place a value of a type
Catinside this box (or a sub-class ofCat). - This means later on, we can put another
Catinside thisgarfieldbox (based on its declared type). - The only methods that are allowed to be invoked on the
garfieldvariable are methods declared in theCatclass (or its super-classes, i.e.AnimalandObject).
Methods with polymorphic parameters
Consider the following method declared in the same class the main() method:
public static void moveAnimalTwice(Animal animal) {
animal.introduce(); // all Animals can introduce() themselves
animal.walk(); // all Animals can walk()
animal.walk();
System.out.println();
}
In very much the same spirit as the Animal lola reference variable above, the Animal animal parameter may refer to an instance of an Animal, a Rabbit or a Cat.
It doesn’t matter which it is, as all Animal types (and sub-types) can introduce() and walk()!
Sure, some of these methods might be overridden by the sub-classes, but we don’t care about that. All we care about is that these methods exist at the Animal type level.
As such, the Animal animal parameter in the moveAnimalTwice() method is similarly said to be polymorphic.
In OOP languages, we often use the term “dynamic dispatch”, or “dynamic binding”, to explain such behaviour:
- The compiler doesn’t commit (at compile time) as to which version of
walk()orintroduce()it should be executing. In fact, it’s not possible for it to commit to a particular version, as it doesn’t know what type theanimalvariable will refer to. - Instead, the decision is deferred to runtime. It dynamically binds (and consequently dispatches) to the appropriate method implementation depending on the particular instance type.
Here’s how we might visualise the animal parameter:
void moveAnimalTwice(Animal animal) { ... }

Quite frankly, the Animal animal parameter of the moveAnimalTwice() method doesn’t care if it is assigned a reference to a Cat or a Rabbit (or any other type). All it cares about is being assigned an Animal instance or a sub-class of Animal. But of course, keep in mind that in our example Animal is abstract class.
Here’s how we might make use of this method:
public static void main(String[] args) {
Animal lola = new Rabbit("Lola Bunny", 1996, Animal.Gender.FEMALE);
Rabbit bugs = new Rabbit("Bugs Bunny", 1938, Animal.Gender.MALE);
Cat garfield = new Cat("Garfield", 1978, Animal.Gender.MALE);
moveAnimalTwice(lola); // hops...
moveAnimalTwice(bugs); // hops...
moveAnimalTwice(garfield); // strolls...
}
Casting object reference types
But what happens if we really need to access some of the sub-type specific logic (whether it be fields or methods)?
Lets have a look at the following example to illustrate. We know that Cats are pretty lazy! They sure aren’t going to move twice unless they’re chasing a mouse! So, let’s revise the moveAnimalTwice() method above as follows:
public static void moveAnimalTwice(Animal animal) {
animal.introduce(); // all Animals can introduce() themselves
animal.walk(); // all Animals can walk()
// check if the instance is actually a Cat. If so, chase a mouse!
if (animal instanceof Cat) {
Cat cat = (Cat) animal; // cast the Animal reference into a Cat reference
cat.chaseMouse(); // access Cat-specific content with the Cat reference
}
animal.walk(); // walk() a second time
}
It’s usually safest to double-check that the animal reference is indeed pointing to an instance of Cat by using the instanceof check. This evaluates to true if the reference denoted by the animal variable indeed refers to an instance of a Cat (or a sub-class of Cat!), otherwise it evaluates to false.
Only if the check succeeds, will we cast the reference to a Cat reference variable (here, Cat cat). This now allows us to access all Cat-specific logic via this cat reference variable.
So, what happens if you incorrectly cast a Rabbit instance to a Cat? Test it out by removing the instanceof check! The compiler won’t care, but at runtime you’ll encounter an unwelcoming ClassCastException!
In summary, here’s what we have done:

On the left is how we can visualise the situation with the Animal animal reference. Even though we might indeed be pointing to a Cat instance, the compiler can never be too sure! So, it will only allow us to access methods declared in the Animal class.
So what we end up doing, is casting this reference (notice it’s the same memory location: 0x790) by declaring another reference of type Cat. We tell the compiler, “trust me, I’m pretty sure the instance that will appear here is indeed a Cat type”. The compiler allows us to do this, and we now have a Cat cat reference. From this reference, we can see everything specific to the Cat class.

