1. Introduction

Understanding how to handle objects within Java’s type hierarchy is essential for writing flexible and maintainable code. Two fundamental concepts in this area are upcasting and downcasting.

In this tutorial, we’ll delve into these concepts, explore their differences, and see practical examples of how they work in Java.

2. Introduction to Casting in Java

Java is an object-oriented programming language that allows the conversion of one type into another within its type system. Casting is the process of converting a reference of one class type into another class type. Specifically, there are two main types of casting in Java: upcasting and downcasting.

To illustrate these concepts, suppose we have a class hierarchy where Dog is a subclass of Animal. The following diagram shows how upcasting and downcasting work within this hierarchy:

Upcasting Vs DowncastingThe upcasting arrow moves from the Dog class to the Animal class, showing how a subclass reference (Dog) can be generalized to a superclass reference (Animal). On the other hand, the downcasting arrow moves from the Animal class back to the Dog class, indicating how a superclass reference (Animal) can be specified again as a subclass reference (Dog).

However, attempting to instantiate a Dog object with an Animal reference would result in a compile-time error due to the incompatibility of types.

3. Upcasting in Java

Upcasting refers to converting a subclass reference into a superclass reference. This type of casting happens implicitly, and we often use it when dealing with polymorphism. Upcasting allows us to treat an object of a subclass as if it were an object of one of its superclasses:

class Animal {
    public void makeSound() {
        System.out.println("Animal sound");
    }
}

class Dog extends Animal {
    public void makeSound() {
        System.out.println("Bark");
    }

    public void fetch() {
        System.out.println("Dog fetches");
    }
}

Here, we have two classes defined: Animal and Dog. The Animal class has a method makeSound() that prints “Animal sound“, while the Dog class, which extends Animal, overrides the makeSound() method to print “Bark” and introduces a new method fetch() that prints “Dog fetches“.

Animal myDog = new Dog();
myDog.makeSound();

In the provided code snippet, we create an instance of the Dog class and assign it to a variable of type Animal. When we call the makeSound() method on myDog, it invokes the overridden method in the Dog class, printing “Bark” to the console.

4. Downcasting in Java

In contrast, downcasting involves converting a superclass reference back into a subclass reference. Unlike upcasting, downcasting is explicit and requires careful handling. Hence, we must ensure that the object being cast matches the target type to avoid a ClassCastException. We use downcasting when we need to access subclass-specific methods or fields that aren’t available in the superclass:

Animal myAnimal = new Dog();
Dog myDog2 = (Dog) myAnimal;
myDog2.makeSound();
myDog2.fetch();

Here, we first upcast a Dog object to an Animal reference. Then, we downcast it back to a Dog reference. This approach allows us to call both the makeSound() method (inherited from the Animal class) and the fetch() method (specific to the Dog class).

Through downcasting, we regain access to the subclass’s methods and fields.

5. Comparing Upcasting and Downcasting

Let’s summarize their key characteristics in the table below to better understand the differences between upcasting and downcasting. This comparison will help clarify when and how to use each technique effectively:

Feature

Upcasting

Downcasting

Definition

Converts a subclass reference to a superclass reference

Converts a superclass reference to a subclass reference

Implicit or Explicit

Implicit

Explicit

Common Usage

Used in polymorphism to generalize an object

Utilized to access subclass-specific functionality

Type Safety

Safe and doesn’t require type-checking

Requires type checking to avoid ClassCastException

Access to Subclass Methods

No, only superclass methods are accessible

Yes, allows access to methods and fields specific to the subclass

Example Scenario

Passing a Dog object to a method expecting an Animal parameter

Casting an Animal reference back to a Dog to call Dog-specific methods

Performance

Generally more efficient due to lack of type checking at runtime

Slightly less efficient due to runtime type checking

6. Conclusion

In conclusion, both upcasting and downcasting are integral to Java’s type system, facilitating flexible and reusable code through inheritance and polymorphism.

As usual, the accompanying source code can be found over on GitHub.