Search code examples
javainheritancesubtype

Why would somebody cast instead of returning a subtype


When overriding an inherited method, is there any reason to not declare the type a sub-type of of its original type? (Animal instead of Bunny see below)

Here is an example with bunnies.

public class Junk2 {

    public static abstract class Animal {
        public abstract String makeSound();
    }

    public static class Bunny extends Animal {

        @Override
        public String makeSound() {
            return "hop hop";
        }

    }

    public static abstract class AnimalHome {

        private final Animal animal;

        public AnimalHome(Animal animal) {
            this.animal = animal;
        }

        public Animal getAnimal() {
            return animal;
        }

    }

    /**
     * I am a bad bunny-hole, you don't know I am giving you a bunny
     *
     */
    public static class BadBunnyHole extends AnimalHome {

        public BadBunnyHole(Animal animal) {
            super(animal);
        }
    }

    /**
     * I am a good bunny-hole, I'm still an AnimalHome because
     * Bunny is a sub-type of Animal
     *
     */
    public static class GoodBunnyHole extends AnimalHome {

        private final Bunny bunny;

        public GoodBunnyHole(Bunny bunny) {
            super(bunny);
            this.bunny = bunny;
        }

        public Bunny getAnimal() {
            return bunny;
        }

    }

    public static void main(String[] args) {
        Bunny bunny = new Bunny();
        GoodBunnyHole goodHole = new GoodBunnyHole(bunny);
        BadBunnyHole badHole = new BadBunnyHole(bunny);

        Bunny bunnyBack1 = goodHole.getAnimal();
        Bunny bunnyBack2 = (Bunny)badHole.getAnimal(); // look now I need to cast, I'm a crap BunnyHole

        System.out.println(bunnyBack1.makeSound());
        System.out.println(bunnyBack2.makeSound());
        System.out.println(bunnyBack1 == bunnyBack2);
    }

}

We can see that with the bad bunny hole one needs to cast to turn the animal back into a bunny. I think this is really bad but I see it allot at the moment, and wonder if there may be a reason for this. This makes as much sense to me as declaring the return type of makseSound as Object instead of a String.

Probably the best bunny-hole would go like this

public static abstract class AnimalHome<T extends Animal> {

        private final T animal;

        public AnimalHome(T animal) {
            this.animal = animal;
        }

        public T getAnimal() {
            return animal;
        }

    }

    public static class BestBunnyHole extends AnimalHome<Bunny> {

        public BestBunnyHole(Bunny animal) {
            super(animal);
        }
    }

But anyway I can't do that in my codebase at the moment. But my question still remains as to why the BadBunnyHole? I have even seen this horrid thing:

public static class BadBunnyHole extends AnimalHome {

        public BadBunnyHole(Animal animal) {
            super(animal);
        }

        public Bunny getBunny() {
            return (Bunny) getAnimal();
        }

}

To me, casting like this smells like a OO design flaw, but is there any reason in Java to do this? Was there a time you couldn't declare the return type a sub-type without breaking the interface? Or maybe some obscure reason if one is working with the reflections library?


Solution

  • When overriding an inherited method, is there any reason to not declare the type a sub-type of of its original type?

    If the method that overrides the superclass method always returns a subtype of the declared return type in the superclass, then there is no reason not to declare the sub-type as the return type in the subclass.

    We can see that with the bad bunny hole one needs to cast to turn the animal back into a bunny. I think this is really bad but I see it a lot at the moment, and wonder if there may be a reason for this.

    I think that many people just don't know that it is possible to make the return type narrower when you override a method.

    I have even seen this horrid thing:

    public static class BadBunnyHole extends AnimalHome {
    
        public BadBunnyHole(Animal animal) {
            super(animal);
        }
    
        public Bunny getBunny() {
            return (Bunny) getAnimal();
        }
    

    That goes to the different between how overriding methods and fields is done. In this case, if you intend to have subclasses of AnimalHome, it's probably not a very good idea to store the animal in the superclass. It's a better idea to make the superclass abstract with an abstract method getAnimal() that is implemented in each subclass, and to let each subclass have storage (a field) of the correct type of animal. Like your GoodBunnyHole example except that AnimalHome is like this:

    public static abstract class AnimalHome {
        public abstract Animal getAnimal();
    }
    

    To me, casting like this smells like a OO design flaw, but is there any reason in Java to do this? Was there a time you couldn't declare the return type a sub-type without breaking the interface? Or maybe some obscure reason if one is working with the reflections library?

    It has "only" been possible to do this since Java 5. From my non-representative experience, many people still don't know that it is possible.

    See also Can overridden methods differ in return type?