nus-cs2113-AY2223S2 / forum

10 stars 0 forks source link

Regarding downcasting code in W5.1 #24

Closed darrenangwx closed 1 year ago

darrenangwx commented 1 year ago

Regarding Cat c = (Cat)a in foo method, does it only work if a is set as Animal a = new DomesticCat(); or Animal a = new Cat(); ? image

nichyjt commented 1 year ago

I thnk that foo() will only work if the underlying object passed into the function is a class DomesticCat. If line 5-7 is removed, then both Cat and Domestic Cat will work.

This is because during runtime, when an object is being casted, Java will look at the underlying object (not its label, the LHS identifier) and see if the underlying object can fit into the casted class type.

If the underlying object cannot be 'matched' to the casted class, it will throw a ClassCastException. 'Matching' here works only when the underlying object is a Subclass (or the class itself) of the casted class.

For example:

// animal is labelled as an Animal, but the underlying object is a DomesticCat on heap memory
Animal animal = new DomesticCat(); 

// The downcasting works because the underlying object 
// is a Domestic Cat even though it was labelled as an Animal
DomesticCat domestic = (DomesticCat) animal; 

// This works too due to similar reasoning
Cat cat = (Cat) animal;

In the context of the code snippet given, this is a minimally reproducible piece of code you can try playing around with! (Tested on an online playground)

class Animal{
    void speak(){
        System.out.println("I'm an animal");
    }
}

class Cat extends Animal{
    @Override
    void speak() {
        System.out.println("I'm a Cat");
    }
}

class DomesticCat extends Cat{
    @Override
    void speak() {
        System.out.println("I'm a DomesticCat");
    }
    void catchMice(){
        System.out.println("I caught a mouse!");
    }
}

public class Main {
    public static void foo(Animal a){
        a.speak();
        Cat c = (Cat)a; // downcast a to a Cat
        c.speak();
        DomesticCat dc = (DomesticCat)a; // downcast a to a DomesticCat
        dc.speak();
        dc.catchMice();
    }

    public static void main(String[] args) {
        Animal animal = new Animal();
        // foo(animal); // error
        Cat cat = new Cat();
        // foo(cat); // error

        Cat cat1 = new DomesticCat();
        foo(cat1); // OK
        System.out.println("-------------");
        Animal stillACat = (Animal) cat1;
        foo(stillACat); // OK
    }
}
jinxuan-owyong commented 1 year ago

If you wish to keep the current implementation of foo() while avoiding ClassCastException, you could also consider checking whether the underlying object is what you expect using instanceof.

Here's an example you can consider:

public static void foo(Animal a) {
    a.speak();
    if (a instanceof Cat) {
      Cat c = (Cat) a; // downcast a to a Cat
        c.speak();
    }
    if (a instanceof DomesticCat) {
        DomesticCat dc = (DomesticCat) a; // downcast a to a DomesticCat
        dc.speak();
        dc.catchMice();
    }
}