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?

Edit

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?

Edit Get your hands dirty!

Before we continue with introducing inheritance, edit the above Repl:

  1. First of all, this code is not gender inclusive! boolean is not a suitable type for gender. So instead, change the code as follows:
    • Inside the Animal class, define the following enum type:
       public enum Gender {FEMALE, MALE, NON_BINARY}
      
    • Remove the use of the boolean isFemale everywhere, and replace it with Gender gender.
    • In the main() method, assign values for the gender. You will write the values as Animal.Gender.FEMALE etc instead of true/false.
    • Print the gender in the introduce() method, or use its gender to determine the corresponding title (e.g., "Mr", "Ms", or "Mx"). Using a switch case statement is a nice way to go about it.
  2. Now implement the walk() method in the Animal class, with a series of if-else statements (or a switch case statement) to check the type:
     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!"); 
     }
    
  3. Wow – that code above looks bad!
  4. Call the walk() method on the Animal instances we have in the main() method.
  5. Mistype one of the animal types (e.g. make lola a Rabitt). The compiler didn’t seem to mind!
  6. We can begin to appreciate that some Animals have the same functionality (e.g. the way they introduce() themselves), but that we might also want a more elegant way to differentiate between different types of Animals.




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.

Edit

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 this as being synonymous with “me” or “my”.
  • We can therefore think of super as 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 type variable – the class itself (i.e. Cat and Rabbit) is the type!
  • We no longer need to specify the number of legs (in the main() method when constructing the instances). A Cat will have 2 legs and a Rabbit will 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 an Animal (parent class).
  • In constructing a Rabbit (child class), we need to first construct an Animal (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:

Edit

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:
    • Edit private, or
    • Edit public.
  • We can also visualise what an enum declaration might look like. We notice this particular enum is:
    • named Gender (which names the type),
    • pubilc, and
    • names three possible values (FEMALE, MALE, and NON_BINARY).

The child classes (Cat and Rabbit)

Let’s now look at the children classes (Cat and Rabbit) that extend Animal:

Edit

The visualisation gives us an appreciation for what it means to say Cat extends Animal:

  • The Cat blueprint quite literally extends the Animal blueprint!
  • There is nothing new added in the Cat (or Rabbit) blueprint sections, other than their respective constructors!
    • All the other logic is in the Animal parent blueprint.

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.


Edit Get your hands dirty!

Let’s understand this a little more:

  1. Add print statements in all three constructors (Animal, Cat, and Rabbit) 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.
  2. We will continue improving this code below…




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:

Edit

Edit Get your hands dirty!

Edit the same code above:

  1. Remove all of the content from the Animal’s walk() method, but keep one of the print statements that says “Don’t know how a … walks!”.
  2. Copy-paste the walk() method signature from Animal into 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 the walk() methods for each respective sub-class, and it’s looking a lot cleaner!
  3. It is a good practice to explicitly state that the walk() method is overriding, by using the @Override annotation.
  4. Add another sub-class (e.g. Dog.java), and add the relevant constructor for that sub-type and also override the walk() method for it. Then create an instance of that sub-class, and check it prints what you expect.
  5. What happens if you create an instance of an Animal in the main() 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 an abstract class!


If you make the changes above, this is what we will ultimately have:

Edit

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 Edit fields and methods will not be accessible to sub-classes (or any other class for that matter),
  • protected Edit 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), and
  • public Edit fields and methods will be accessible to sub-classes (and also other classes that are not sub-classes).

Edit Get your hands dirty!

Continue editing the same code above:

  1. In the sub-classes (e.g. the overridden walk() method), try to print out the animal’s name. This is currently declared as private in the Animal class, so access is not inherited by the Cat (or Rabbit) sub-class.
  2. Change the modifier of the instance fields inside the Animal class to protected instead of private.
  3. You should now be able to access these instance fields in the sub-classes and use them.
  4. Keep the yearNow static field as private, as it’s not going to be needed for the sub-classes as it’s only something that the Animal class uses to figure out the animal’s age.


If you make the changes above, this is what we will ultimately have:

Edit

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 Edit versus public Edit?

  • 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 Edit, 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 Animal class (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 the Animal class (or part of the same package).
    • With public Edit, 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).




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 Cat will say “Meow”, and
    • A Rabbit will say “Prrrr”.
  • 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 Animal class (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 super keyword 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()
    }
}

Edit Get your hands dirty!

Continue editing the same code above:

  1. @Override the introduce() methods for the sub-classes, while also reusing the Animal’s introduce() method (as in the example above). Do this for all your sub-classes.
  2. Define a public String getTitledName() instance method in the Animal class:
    • Use a switch statement on the gender, returning the animal’s name with the title:
      • Gender.FEMALE: returns "Ms <name>"
      • Gender.MALE: returns "Mr <name>"
      • Gender.NON_BINARY: returns "Mx <name>"
  3. @Override the public String getTitledName() method for the Cat class:
    • Regardless of gender, cats demand respect. So, return "Royal <name>".
  4. Make use of the getTitledName() method as part of the Animal’s introduce() 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:

Edit

  • There is a public getTitledName() method defined in the Animal class, so the children will inherit this functionality.
    • The Cat class is not happy with that implementation, and so it overrides it with its own definition.
    • The Rabbit class is happy with the existing implementation provided in the Animal class, and therefore does not override it.
  • Both the Catand Rabbit classes override the introduce() method, but they still also make use of the original introduce() logic defined in the Animal class.
  • Both the Catand Rabbit classes override the walk() method, and completely disregard the original walk() logic defined in the Animal class.




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!

Edit




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.

Edit Get your hands dirty!

Continue editing the same code above:

@Override the toString() of some of our classes.

  • First override it in the Animal class, then see what the output is when you print the animal instance.
  • Then override the toString() method in the Cat class (not Rabbit). Observe the output from both cats and rabbits.
  • Comment out the toString() in the Animal class. 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!

Edit Get your hands dirty!

Continue editing the same code above:

  1. Make the Animal class abstract.
  2. The compiler will now prohibit you from creating an instance of Animal! Nice.
  3. What happens if you comment out the walk() method from the Cat class? How will cats walk? Looks like it will use the original walk() method defined in the Animal class. Does this make sense? Maybe we should force all sub-classes of Animal to define a walk() method!
  4. In the Animal class, remove the body of the walk() method and replace it with a semi-colon and add the abstract keyword, i.e:
    public abstract class Animal {
      ...
      public abstract void walk();
    }
    
  5. Since the Animal class is abstract, it’s allowed to have such “empty” methods (but these empty methods need to be declared as abstract also, just like the class is declared as abstract). The Animal class 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!”
  6. What it means is, we must provide a walk() implementation of the Cat class (so bring back the walk() method you commented out above). If you don’t, then you must make the Cat an abstract class also! 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:

Edit

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:

Edit

  • The Animal class remains an abstract class.
  • The extending child classes (Cat and Rabbit) are not abstract, because they provide an implementation for all of Animal’s abstract methods (namely walk()).
  • Even though we can no longer create just a “pure” Animal instance, we still end up creating an Animal instance in the process of creating a Cat instance.
  • If a class (e.g. Insect) were to extend the Animal class, and not provide an implementation for the walk() method, then that Insect class will remain an abstract class.




Generalisation and specialisation

Edit

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 Animals introduce() themselves and have legs.
  • Abstraction is closely related to generalisation, where we ignore subtle sub-type differences (e.g. all of our Animals walk()—albeit differently).

Specialisation:

  • Sub-types have their own particular methods and fields.
  • For example, only Rabbits can carryEasterEgg(Egg egg), while Cats can chaseMouse(Mouse mouse).

Here’s how we might specialise the child classes, by adding methods that don’t exist in the parent class:


Edit Get your hands dirty!

Edit the code above as follows:

  • Specialise the Rabbit class some more:
    • Add a private String favVegetable instance field,
    • Add a parameter to the Rabbit constructor to set the field, and
    • Add a getter getFavVegetable().
  • Specialise the Cat class some more:
    • Add a private int numLivesRemaining instance field,
    • Initialise the field to 9 (all Cats start off with 9 lives), and
    • Add a getter getNumLivesRemaining().
  • Complete the Egg class:
    • Add a simple constructor requires a String name for the egg being created, then
    • @Override the toString() for the Egg, so it prints its name.
  • Complete the Mouse class:
    • The Mouse class extends the Animal class,
    • Add a simple constructor, much like the Cat or Rabbit,
    • Implement all the methods that are required (notice that Animal is an abstract class),
    • Add a runAway() instance method for the Mouse, which prints the mouse is scared and running away.
  • 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.
    • Update the chaseMouse() method to:
      • Take a parameter of type Mouse,
      • It should print which mouse the Cat is chasing, and
      • Invoke that mouse’s runAway() method.


If you make the changes above, this is what we end up with:

Edit

  • The Cat class contains a new field numLivesRemaining that does not exist in the Animal class.
  • The Cat class contains a new method getNumLivesRemaining() that does not exist in the Animal class.
  • The Cat class contains a new method chaseMouse() that does not exist in the Animal class.
  • The Rabbit class contains a new field favVegetable that does not exist in the Animal class.
  • The Rabbit class contains a new method getFavVegetable() that does not exist in the Animal class.
  • The Rabbit class contains a new method carryEasterEgg() that does not exist in the Animal class.




Visualising instances with inheritance

Here’s how we can visualise our instances following the same visual conventions we have been seeing before:

Edit

The instances are still represented as pieces of paper.

We can appreciate why we need to first call the parent class’ constructor:

  • Since Rabbit and Cat instances are still partially Animal instances, we see the common Animal fields 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

Edit

Po ■ ly-

  1. Prefix from Ancient Greek, many or much.


-morph

  1. 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);

Edit

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 Animal inside this box – as long as it’s an Animal or a sub-class of Animal.
  • This means later on, we can put a different kind of Animal inside this lola box (e.g. an instance of a Cat).
  • The only methods that are allowed to be invoked on the lola variable are methods declared in the Animal class (or its super-class, e.g. Object).
  • Similarly, the only fields that may be accessed externally on the lola variable are fields declared in the Animal class.
    • Note: don’t confuse this with fields in general declared in the Rabbit class (e.g. favVegetable). They are still accessible once polymorphism takes us into the context of the Rabbit class (e.g. when we execute an overridden method).
  • Why can’t we access Rabbit functionality through the lola variable?
    • lola might later on refer to an instance of a Cat (or some other Animal sub-class).
  • The lola reference variable is therefore polymorphic.



Rabbit bugs = new Rabbit("Bugs Bunny", 1938, Gender.MALE);

Edit

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 Rabbit inside this box (or a sub-class of Rabbit).
  • This means later on, we can put another Rabbit inside this bugs box (based on its declared type).
  • The only methods that are allowed to be invoked on the bugs variable are methods declared in the Rabbit class (or its super-classes, i.e. Animal and Object).



Cat garfield = new Cat("Garfield", 1978, Gender.MALE);

Edit

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 Cat inside this box (or a sub-class of Cat).
  • This means later on, we can put another Cat inside this garfield box (based on its declared type).
  • The only methods that are allowed to be invoked on the garfield variable are methods declared in the Cat class (or its super-classes, i.e. Animal and Object).



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() or introduce() 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 the animal variable 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) { ... }

Edit

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:

Edit

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.