Classes & OOP: Upcasting and Downcasting

Roger PoonBy Roger Poon

JS++ Designer and Project Lead

Now that we understand both subtyping and static versus dynamic polymorphism, we can learn about upcasting and downcasting.

Upcasting and downcasting is based on type relationships. In other words, if you have data of type 'Animal', you can "downcast" it to its subtype 'Dog'. Conversely, if you have data of type 'Dog', you can "upcast" it to its supertype 'Animal'. However, you cannot cast data of either type to 'int' because there is no type relationship between 'int' and 'Animal' or 'Dog'.

Since we are now equipped with an understanding of compile-time versus runtime polymorphism, and why and how the compiler resolved the 'render' method when the data type was specified as 'Animal', let's restore our main.jspp code:

    import Animals;

    Animal cat1 = new Cat("Kitty");
    cat1.render();
    Animal cat2 = new Cat("Kat");
    cat2.render();
    Animal dog = new Dog("Fido");
    dog.render();
    Animal panda = new Panda();
    panda.render();
    Animal rhino = new Rhino();
    rhino.render();
    

As you'll recall, this is the code that rendered all our animals, but we no longer got the animal names on mouse over. One way to remedy this is with a downcast:

    import Animals;

    Animal cat1 = new Cat("Kitty");
    ((Cat) cat1).render();
    Animal cat2 = new Cat("Kat");
    ((Cat) cat2).render();
    Animal dog = new Dog("Fido");
    ((Dog) dog).render();
    Animal panda = new Panda();
    panda.render();
    Animal rhino = new Rhino();
    rhino.render();
    

The syntax for a type cast is:

(type) expression

For example:

(int) 100

The reason there are extra parentheses in our revised main.jspp code is because we want the type cast to take precedence so that the cast occurs before the 'render' method is called. See the JS++ operator precedence table for more details.

If you compile and run the code now, you should see that the cats and dogs once again show their names when you hover your mouse over their icon.

To illustrate upcasting, we can also cast one of our cats to the 'Animal' type. Calling the 'render' method on this cat will mean it uses the 'Animal' class render() method which will not include the name:

    import Animals;

    Animal cat1 = new Cat("Kitty");
    ((Cat) cat1).render();
    Cat cat2 = new Cat("Kat");
    ((Animal) cat2).render();
    Animal dog = new Dog("Fido");
    ((Dog) dog).render();
    Animal panda = new Panda();
    panda.render();
    Animal rhino = new Rhino();
    rhino.render();
    

Notice that I changed the type of 'cat2' back to 'Cat' in order to illustrate upcasting. From there, I upcast 'cat2' to 'Animal'. If you compile the code now, you will notice the first cat ("Kitty") has its name shown on mouse over, but the second cat ("Kat") does not have the same behavior.

While we were able to resolve the correct 'render' method using casts, this is not the most elegant way to resolve the method. For example, if we had an array of 'Animal' objects, our loop would be nested with 'if' statements performing 'instanceof' checks and subsequent type casts. This is not ideal and leads to hard-to-read code. There is a more elegant way: virtual methods.