1. Typecasting

Variables that store reference types (classes) can also be converted to different types. But this only works within a single type hierarchy. Let's look at a simple example. Suppose we have the following class hierarchy, in which the classes below inherit the classes above.

Typecasting

Typecasting of reference types as well as primitive ones is also categorized as either widening and narrowing.

We see that the Cat class inherits the Pet class, and the Pet class, in turn, inherits the Animal class.

If we write code like this:

Animal kitten = new Cat();

This is a widening type conversion. It is also called an implicit cast. We've widened the cat reference so that it now refers to a Cat object. With a type conversion like this, we will not be able to use the kitten reference to call methods that are present in the Cat class but absent in the Animal class.

A narrowing conversion (or explicit cast) happens in the opposite direction:

Cat cat = (Cat) kitten;

We explicitly indicated that we want to cast the reference stored in the kitten variable (whose type is Animal) to the Cat type.



2. Checking the type of an object

But you need to be very careful here. If you do this:

Animal beast = new Cat();
Wolf grayWolf = (Wolf) beast;

The compiler will allow this code, but there will be an error when the program runs! The JVM will throw an exception:

Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to a Wolf

References to a Cat object can only be stored in variables whose type is an ancestor of the Cat class: Pet, Animal, or Object.

Why is that?

The relevant point here is that an object reference is used to refer to the methods and variables of that object. And there won't be any problems if we use an Animal variable to store a reference to a Cat object: the Cat type always has a variables and methods of the Animal type — it inherited them!

But if the JVM allowed us to store a reference to a Cat object in a Wolf variable, then we might have a situation where we might try to use the grayWolf variable to call a method that does not exist in the Cat object stored in that variable. That's why this arrangement is not allowed.

Java has a special instanceof operator that lets you check if an object is of a certain type and therefore can be stored into a variable of a certain type. It looks quite simple:

variable instanceof Type

Example:

Animal beast = new Cat();
if (beast instanceof Wolf)
{
   Wolf grayWolf = (Wolf) beast;
}

This code won't cause errors — even at runtime.

Here are some more examples that illustrate the situation:

Widening type conversion Description
Cow cow = new Whale();

This is a classic widening conversion — no type conversion operator is required. Now only the methods defined in the Cow class can be called on the Whale object.

On the cow variable, the compiler will only let you call methods that its type (the Cow class) has.

Narrowing type conversion
Cow cow = new Whale();
if (cow instanceof Whale)
{
   Whale whale = (Whale) cow;
}
Classic narrowing conversion: You need to add a type check and a cast operator.
The Cow cow variable stores a reference to a Whale object.
We verify that this is the case, and then perform a (narrowing) type conversion. Or as it is also called:
a type cast
.

Cow cow = new Cow();
Whale whale = (Whale) cow; // Exception
You can narrow a reference type without checking the type of the object.
If the cow variable refers to an object that is not a Whale, then an InvalidClassCastException will be generated.


3. Calling the original method: the super keyword

When overriding a parent class's method, sometimes rather than replacing it with our own, we only want to supplement it slightly.

It would be cool if we could the parent class's method in our method, and then execute some of our own code. Or perhaps first execute our own code, and then call the parent class's method.

And Java lets us to just that. To call a method of the parent class, do this:

super.method(arguments);

Examples:

class PeaceTime
{
   public double getPi()
   {
      return 3.14;
   }
}

class WarTime extends PeaceTime
{
   public double getPi()
   {
      return super.getPi()*2;  // 3.14*2
   }
}

In wartime, the value of Pi can be greater than 6! Of course, we're joking, but this example demonstrates how this all can work.

Here are a couple more examples to clarify things a bit:

Code Description
class Cow
{
   public void printAll()
   {
      printColor();
      printName();
   }

   public void printColor()
   {
      System.out.println("I'm a white whale");
   }

   public void printName()
   {
      System.out.println("I'm a cow");
   }
}

class Whale extends Cow
{
   public void printName()
   {
      System.out.print("This is incorrect: ");
      super.printName();
      System.out.println("I'm a whale");
   }
}
Cow and Whale classes
public static void main(String[] args)
{
   Whale whale = new Whale();
   whale.printAll();
}
The screen output will be:
I'm a white whale
This is incorrect: I'm a cow
I'm a whale

This is hard stuff. Honestly, it's one of the hardest things in OOP. That said, you do need to know and understand it.