Question

My question is about a special case of the super class Animal.

  1. My Animal can moveForward() and eat().
  2. Seal extends Animal.
  3. Dog extends Animal.
  4. And there's a special creature that also extends Animal called Human.
  5. Human implements also a method speak() (not implemented by Animal).

In an implementation of an abstract method which accepts Animal I would like to use the speak() method. That seems not possible without doing a downcast. Jeremy Miller wrote in his article that a downcast smells.

What would be a solution to avoid downcasting in this situation?

Was it helpful?

Solution

If you have a method that needs to know whether the specific class is of type Human in order to do something, then you are breaking some SOLID principles, particularly :

  • Open/closed principle - if, in the future, you need to add a new animal type that can speak (for example, a parrot), or do something specific for that type, your existing code will have to change
  • Interface segregation principle - it sounds like you are generalizing too much. An animal can cover wide spectrum of species.

In my opinion, if your method expects a particular class type, to call it's particular method, then change that method to accept only that class, and not it's interface.

Something like this :

public void MakeItSpeak( Human obj );

and not like this :

public void SpeakIfHuman( Animal obj );

OTHER TIPS

The problem is not that you are downcasting - it's that you are downcasting to Human. Instead, create an interface:

public interface CanSpeak{
    void speak();
}

public abstract class Animal{
    //....
}

public class Human extends Animal implements CanSpeak{
    public void speak(){
        //....
    }
}

public void mysteriousMethod(Animal animal){
    //....
    if(animal instanceof CanSpeak){
        ((CanSpeak)animal).speak();
    }else{
        //Throw exception or something
    }
    //....
}

This way, the condition is not that the animal is Human - the condition is that it can speak. This means mysteriousMethod can work with other, non-human subclasses of Animal as long as they implement CanSpeak.

You could add Communicate to Animal. Dog barks, Human speaks, Seal.. uhh.. I don't know what seal does.

But it sounds like your method is designed to if(Animal is Human) Speak();

The question you may want to ask is, what is the alternative? Its hard to give a suggestion since I don't know exactly what you want to achieve. There are theoretical situations where downcasting/upcasting is the best approach.

In this case, the default implementation of speak() in the AbstractAnimal class would be:

void speak() throws CantSpeakException {
  throw new CantSpeakException();
}

At that point, you've got a default implementation in the Abstract class - and it behaves correctly.

try {
  thingy.speak();
} catch (CantSeakException e) {
  System.out.println("You can't talk to the " + thingy.name());
}

Yes, this means you've got try-catches scattered through the code to handle every speak, but the alternative to this is if(thingy is Human) wrapping all the speaks instead.

The advantage of the exception is that if you have another type of thing at some point that speaks (a parrot) you won't need to reimplement all of your tests.

In an implementation of an abstract method which accepts Animals I would like to use the speak() method.

You have a few choices:

  • Use reflection to call speak if it exists. Advantage: no dependency on Human. Disadvantage: there is now have a hidden dependency on the name "speak".

  • Introduce a new interface Speaker and downcast to the interface. This is more flexible than depending on a specific concrete type. It has the disadvantage that you have to modify Human to implement Speaker. This won't work if you can't modify Human

  • Downcast to Human. This has the disadvantage that you will have to modify the code whenever you want another subclass to speak. Ideally you want to extend applications by adding code without repeatedly going back and changing old code.

Downcasting is sometimes necessary and appropriate. In particular, it's often appropriate in cases where one has objects that may or may not have some ability, and one wishes to use that ability when it exists while handling objects without that ability in some default fashion. As a simple example, suppose a String is asked whether it is equal to some other arbitrary object. For one String to equal another String, it must examine the length and backing character array of the other string. If a String is asked whether it equals a Dog, however, it cannot access the length of the Dog, but it shouldn't have to; instead, if the object to which a String is supposed to compare itself isn't a String, the comparison should use a default behavior (reporting that the other object isn't equal).

The time when downcasting should be regarded as most dubious is when the object being cast is "known" to be of the proper type. In general, if an object is known to be a Cat, one should use a variable of type Cat, rather than a variable of type Animal, to refer to it. There are times when this doesn't always work, however. For example, a Zoo collection might hold pairs of objects in even/odd array slots, with the expectation that the objects in each pair will be able to act upon each other, even if they cannot act upon the objects in other pairs. In such a case, the objects in each pair would still have to accept a non-specific parameter type such that they could, syntactically, be passed the objects from any other pair. Thus, even if Cat's playWith(Animal other) method would only work when other was a Cat, the Zoo would need to be able to pass it an element of an Animal[], so its parameter type would have to be Animal rather than Cat.

In cases where downcasting is legitimately unavoidable, one should use it without qualm. The key question is determining when one can sensibly avoid downcasting, and avoiding it when sensibly possible.

Licensed under: CC-BY-SA with attribution
scroll top