Automation QA Testing Course Content

Upcasting & Downcasting in Java with Examples | Full Tutorial

 

Upcasting & Downcasting in Java with Examples | Full Tutorial

Java is an object-oriented programming language that allows developers to create classes and objects. Upcasting and downcasting are two concepts that are related to class inheritance and object polymorphism.

Upcasting In Java and How Does It work?

It is a process of converting a subclass object to its superclass reference type. In other words, it is a way of treating an object of a subclass as an object of its superclass. This conversion is done implicitly by the Java Virtual Machine (JVM) at runtime.

When upcasting is performed, the subclass object loses its specific attributes and methods that are not present in its superclass. The resulting object can only access the methods and attributes that are defined in the superclass.

Upcasting is useful when you want to use a single reference type to refer to objects of different subclasses. This allows you to write more generic code that can handle different types of objects without having to write separate code for each type.

Example of Upcasting in Java

Let’s consider an example of upcasting in Java. Suppose we have a class hierarchy that looks like this:

// Superclass
class Animal {
    String name = "Generic Animal";  // Instance variable
    int age = 5;  // Instance variable

    // Constructor
    Animal(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Animal constructor called");
    }

    void makeSound() {  // Overridden method
        System.out.println("Animal makes a generic sound");
    }

    void eat() {  // Overridden method
        System.out.println("Animal eats food");
    }

    void sleep() {  // Not overridden
        System.out.println("Animal sleeps");
    }

    void move() {  // Not overridden
        System.out.println("Animal moves around");
    }

    void displayInfo() {  // Overridden method
        System.out.println("Animal Name: " + name + ", Age: " + age);
    }
}

// Subclass
class Dog extends Animal {
    String name = "Dog";  // Hiding superclass instance variable
    String breed;  // Additional instance variable

    // Constructor
    Dog(String name, int age, String breed) {
        super(name, age);  // Call superclass constructor
        this.breed = breed;
        System.out.println("Dog constructor called");
    }

    @Override
    void makeSound() {  // Overriding method
        System.out.println("Dog barks");
    }

    @Override
    void eat() {  // Overriding method
        super.eat();  // Calling superclass method using 'super'
        System.out.println("Dog eats bones");
    }

    @Override
    void displayInfo() {  // Overriding method
        super.displayInfo();  // Call superclass method
        System.out.println("Breed: " + breed);
    }

    void guardHouse() {  // Subclass-specific method
        System.out.println("Dog guards the house");
    }
}

// Main class
public class MethodOverridingDemo {
    public static void main(String[] args) {
        System.out.println("---- Creating Animal Object ----");
        Animal animal1 = new Animal("Wild Animal", 8);
        animal1.makeSound();
        animal1.eat();
        animal1.sleep();
        animal1.move();
        animal1.displayInfo();
        System.out.println();

        System.out.println("---- Creating Dog Object ----");
        Dog dog1 = new Dog("Buddy", 3, "Golden Retriever");
        dog1.makeSound();
        dog1.eat();
        dog1.sleep();  // Not overridden, so calls Animal's method
        dog1.move();   // Not overridden, so calls Animal's method
        dog1.displayInfo();
        dog1.guardHouse();
        System.out.println();

        System.out.println("---- Upcasting (Animal reference to Dog object) ----");
        Animal animal2 = new Dog("Rocky", 4, "German Shepherd");
        animal2.makeSound();  // Calls Dog's overridden method
        animal2.eat();  // Calls Dog's overridden method
        animal2.sleep();  // Calls Animal's method (not overridden)
        animal2.move();   // Calls Animal's method (not overridden)
        animal2.displayInfo();  // Calls Dog's overridden method
        System.out.println("Animal name (Upcasting): " + animal2.name);  // Access Animal's instance variable
        System.out.println();

        System.out.println("---- Downcasting (Casting back to Dog) ----");
        if (animal2 instanceof Dog) {
            Dog dog2 = (Dog) animal2;
            System.out.println("Dog name: " + dog2.name);  // Access Dog's instance variable
            dog2.makeSound();
            dog2.eat();
            dog2.guardHouse();  // Calls Dog-specific method
        }
    }
}

In this code, we create a Dog object called myDog. We then upcast myDog to an Animal reference called myAnimal. Now, myAnimal can only access the methods and attributes that are defined in the Animal class:

myAnimal.makeSound(); // prints “Dog is barking”


Downcasting in Java and How Does It Work?

Downcasting is the opposite of upcasting. It is a process of converting a superclass reference type to its subclass object. In other words, it is a way of treating an object of a superclass as an object of its subclass. This conversion is done explicitly by the programmer using the cast operator.

When downcasting is performed, the superclass reference is checked to see if it refers to an object of the subclass. If it does, the reference is converted to the subclass type. If it doesn’t, a ClassCastException is thrown at runtime.

Downcasting is useful when you want to access the specific methods and attributes of a subclass object that are not present in its superclass. However, it should be used with caution because it can lead to runtime errors if the superclass reference does not refer to an object of the subclass.

Example of Downcasting in Java

Let’s consider an example of downcasting in Java. Suppose we have the same class hierarchy as before:

class Animal

{

public void makeSound()

{

System.out.println("Animal is making a sound");

}

}

class Dog extends Animal

{

public void makeSound()

{ System.out.println("Dog is barking");

}

public void fetch() {

System.out.println("Dog is fetching");

}

}

// Main class public class MethodOverridingDemo { public static void main(String[] args) { System.out.println("---- Creating Animal Object ----"); Animal animal1 = new Animal("Wild Animal", 8); animal1.makeSound(); animal1.eat(); animal1.sleep(); animal1.move(); animal1.displayInfo(); System.out.println(); System.out.println("---- Creating Dog Object ----"); Dog dog1 = new Dog("Buddy", 3, "Golden Retriever"); dog1.makeSound(); dog1.eat(); dog1.sleep(); // Not overridden, so calls Animal's method dog1.move(); // Not overridden, so calls Animal's method dog1.displayInfo(); dog1.guardHouse(); System.out.println(); System.out.println("---- Upcasting (Animal reference to Dog object) ----"); Animal animal2 = new Dog("Rocky", 4, "German Shepherd"); animal2.makeSound(); // Calls Dog's overridden method animal2.eat(); // Calls Dog's overridden method animal2.sleep(); // Calls Animal's method (not overridden) animal2.move(); // Calls Animal's method (not overridden) animal2.displayInfo(); // Calls Dog's overridden method System.out.println("Animal name (Upcasting): " + animal2.name); // Access Animal's instance variable System.out.println(); System.out.println("---- Downcasting (Casting back to Dog) ----"); if (animal2 instanceof Dog) { Dog dog2 = (Dog) animal2; System.out.println("Dog name: " + dog2.name); // Access Dog's instance variable dog2.makeSound(); dog2.eat(); dog2.guardHouse(); // Calls Dog-specific method } } }

Now, let’s create an Animal object and downcast it to a Dog reference:

Animal myAnimal = new Dog(); Dog myDog = (Dog) myAnimal;

In this code, we create an Animal object called myAnimal. We then downcast myAnimal to a Dog reference called myDog. Now, myDog can access the specific methods and attributes that are defined in the Dog class:

myDog.makeSound(); // prints “Dog is barking” myDog.fetch(); // prints “Dog is fetching”

Difference Between Upcasting and Downcasting

The main difference between upcasting and downcasting is the direction of the conversion. Upcasting converts a subclass object to its superclass reference, while downcasting converts a superclass reference to its subclass object.

Upcasting is done implicitly by the JVM at runtime, while downcasting is done explicitly by the programmer using the cast operator.

It is safe because it only allows access to the methods and attributes that are defined in the superclass. Downcasting, on the other hand, can be dangerous because it can lead to runtime errors if the superclass reference does not refer to an object of the subclass.

Good to Read:- Top Features of JavaScript You Must Know

When to Use Upcasting and Downcasting in Java?

Upcasting is useful when you want to write generic code that can handle different types of objects without having to write separate code for each type. It is also useful when you want to create collections of objects that have a common superclass.

Downcasting is useful when you want to access the specific methods and attributes of a subclass object that are not present in its superclass. However, it should be used with caution because it can lead to runtime errors if the superclass reference does not refer to an object of the subclass.

Best Practices for Upcasting and Downcasting in Java

Here are some best practices for using upcasting and downcasting in Java:

  • Use upcasting to write generic code that can handle different types of objects without having to write separate code for each type.
  • Use downcasting sparingly and only when you need to access the specific methods and attributes of a subclass object that are not present in its superclass.
  • Always check the type of the object before downcasting to avoid runtime errors.
  • Avoid using downcasting in performance-critical code because it can be slow.

Common Mistakes to Avoid When Using Upcasting and Downcasting in Java

Here are some common mistakes to avoid when using upcasting and downcasting in Java:

  • Using downcasting without checking the type of the object first can lead to runtime errors.
  • It excessively can make the code difficult to read and maintain.
  • Using upcasting with complex object hierarchies can result in unexpected behavior.
  • Forgetting to cast the object before downcasting can also lead to runtime errors.

Conclusion

In conclusion, upcasting and downcasting are two important concepts in Java that allow developers to work with class inheritance and object polymorphism. Upcasting is the process of converting a subclass object to its superclass reference, while downcasting is the process of converting a superclass reference to its subclass object.

Upcasting is useful for writing generic code that can handle different types of objects, while downcasting is useful for accessing the specific methods and attributes of a subclass object that are not present in its superclass. However, downcasting should be used with caution because it can lead to runtime errors.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.