11. Inheritance and Polymorphism

11.3. Abstract Classes, Interfaces, and Polymorphism

In Java, there are three kinds of polymorphism:

Overriding an inherited method.

Implementing an abstract method.

Implementing a Java interface.

In the previous section we saw examples of the first type of polymor- phism. All forms of polymorphism are based on Java’s dynamic binding mechanism. In this section we will develop an example that illustrates the other two types of polymorphism and discuss some of the design implications involved in choosing one or the other approach.

 

 

 

 

 

 

 

Extensibility


Implementing an Abstract Method

An important feature of polymorphism is the ability to invoke a polymor- phic method that has been defined only abstractly in the superclass. To illustrate this feature, we will develop a hierarchy of simulated animals that make characteristic animal sounds, an example that is widely used to illustrate polymorphism.

As we all know from our childhood, animals have distinctive ways of speaking. A cow goes “moo”; a pig goes “oink”; and so on. Let’s design a hierarchy of animals that simulates this characteristic by printing the characteristic sounds that these animals make. We want to design our classes so that any given animal will return something like “I am a cow and I go moo,” when we invoke the toString() method. Moreover, we want to design this collection of classes so that it is extensible—that is, so that we can continue to add new animals to our menagerie without having to change any of the code in the other classes.

Figure 8.5 provides a summary of the design we will implement. The Animal class is an abstract class. That’s why its name is italicized in the UML diagram. The reason that this class is abstract is because its speak() method is an abstract method, which is a method definition that does not contain an implementation. That is, the method definition contains just the method’s signature, not its body. Any class that contains an abstract method, must itself be declared abstract. Here is the definition of the Animal class:

,,

 

 

 

Figure 8.5: The Animal class hier- archy.

 

 

 

J

Note how we declare the abstract method (speak()) and the abstract class. Because one or more of its methods is not implemented, an abstract class cannot be instantiated. That is, you cannot say:

,,

 

 

 

 

 

Rules for abstract classes


J

Even though it is not necessary, we give the Animal class a constructor. If we had left this off, Java would have supplied a default constructor that would be invoked when Animal subclasses are created.

Java has the following rules on using abstract methods and classes.

Any class containing an abstract method must be declared an

abstract class.

An abstract class cannot be instantiated. It must be subclassed.

A subclass of an abstract class may be instantiated only if it im- plements all of the superclass’s abstract methods. A subclass

 

that implements only some of the abstract methods must itself be declared abstract.

 

 

 

A class may be declared abstract even it contains no abstract methods. It could, for example, contain instance variables that are common to all its subclasses.

 

 

Even though an abstract method is not implemented in the superclass, it can be called in the superclass. Indeed, note how the toString() method calls the abstract speak() method. The reason that this works in Java is due to the dynamic binding mechanism. The polymorphic speak() method will be defined in the various Animal subclasses. When the Animal.toString() method is called, Java will decide which actual speak() method to call based on what subclass of Animal is involved.

Definitions for two such subclasses are shown in Figure 8.6. In each

 

,,

 

 

 

 

 

 

 

 

 

 

 

 

J

Figure 8.6: Two Animal subclasses.

 

 

case the subclass extends the Animal class and provides its own con- structor and its own implementation of the speak() method. Note that in their respective constructors, we can refer to the kind instance vari- able, which is inherited from the Animal class. By declaring kind as a protected variable, it is inherited by all Animal subclasses but hid- den from all other classes. On the other hand, if kind had been declared public, it would be inherited by Animal subclasses, but it would also be accessible to every other class, which would violate the information hiding principle.

 

Given these definitions, we can now demonstrate the power and flex- ibility of inheritance and polymorphism. Consider the following code segment:

,,

 

 

 

 

 

 

 

 

 

Advantage of polymorphism


J

We first create a Cow object and then invoke its (inherited) toString() method. It returns, “I am a cow and I go moo.” We then create a Cat object and invoke its (inherited) toString() method, which returns, “I am a cat and I go meow.” In other words, Java is able to determine the appropriate implementation of speak() at run time in each case. The invocation of the abstract speak() method in the Animal.toString() method is a second form of polymorphism.

What is the advantage of polymorphism here? The main advantage is the extensibility that it affords our Animal hierarchy. We can define and use completely new Animal subclasses without redefining or recompiling the rest of the classes in the hierarchy. Note that the toString() method in the Animal class does not need to know what type of Animal sub- class will be executing its speak() method. The toString() method will work correctly for any subclass of Animal because every non-abstract subclass of Animal must implement the speak() method.

To get a better appreciation of the flexibility and extensibility of this design, it might be helpful to consider an alternative design that does not use polymorphism. One such alternative would be to define each Animal subclass with its own speaking method. A Cow would have a moo() method; a Cat would have a meow() method; and so forth. Given this design, we could use a switch statement to select the appropriate method call. For example, consider the following method definition:

 

,,

 

 

 

 

 

 

 

 

 

 

 

 

Extensibility


J

In this example, we introduce the instanceof operator, which is a built- in boolean operator. It returns true if the object on its left-hand side is an instance of the class on its right-hand side.

The talk() method would produce more or less the same result. If you call talk(new Cow()), it will return “I am a cow and I go moo.” However, with this design, it is not possible to extend the Animal hierar- chy without rewriting and recompiling the talk() method.

Thus, one of the chief advantages of using polymorphism is the great

flexibility and extensibility it affords. We can define new Animal sub- classes and define their speak() methods. These will all work with the

 

toString() method in the Animal class, without any need to revise that method.

Another advantage of using abstract methods is the control that it gives the designer of the Animal hierarchy. By making it an abstract class with an abstract speak() method, any non-abstract Animal subclass must im- plement the speak() method. This lends a great degree of predictabil- ity to the subclasses in the hierarchy, making it easier to use them in applications.

SELF-STUDY EXERCISES

EXERCISE 8.7Following the examples in this section, define an

Animal subclass named Pig, which goes “oink.”

EXERCISE 8.8Show how you would have to modify the talk()

method defined above to incorporate the Pig class.

Implementing a Java Interface

A third form of polymorphism results through the implementation of Java

interfaces, which are like classes but contain only abstract method def-

initions and constants (final) variables. An interface cannot containJava interface

instance variables. We have already seen interfaces, such as when we encountered the ActionListener interface in Chapter 4.

The designer of an interface specifies what methods will be imple-

mented by classes that implement the interface. This is similar to what we did when we implemented the abstract speak() method in the animal example. The difference between implementing a method from an inter- face and from an abstract superclass is that a subclass extends an abstract superclass but it implements an interface.

Java’s interface mechanism gives us another way to design polymor- phic methods. To see how this works, we will provide an alternative design for our animal hierarchy. Rather than defining speak() as an abstract method within the Animal superclass, we will define it as an abstract method in the Speakable interface (Fig. 8.7).

,,

 

 

 

 

 

 

 

 

J

Figure 8.7: Defining and using the Speakable interface.

 

Note the differences between this definition of Animal and the pre- vious definition. This version no longer contains the abstract speak() method. Therefore, the class itself is not an abstract class. However, be- cause the speak() method is not declared in this class, we cannot call the

 

 

 

Cast operation


speak() method in the toString() method, unless we cast this object into a Speakable object.

We encountered the cast operation in Chapter 5, where we used it with primitive types such as (int) and (char). Here, we use it to specify the actual type of some object. In this toString() example, this ob- ject is some type of Animal subclass, such as a Cat. The cast operation, (Speakable), changes the object’s actual type to Speakable, which syntactically allows its speak() method to be called.

Given these definitions, Animal subclasses will now extend the

Animal class and implement the Speakable interface:

,,

 

 

 

 

 

 

J

To implement a Java interface, one must provide a method implementa- tion for each of the abstract methods in the interface. In this case there is only one abstract method, the speak() method.

Note, again, the expression from the Animal.toString() class

,,

 

 

 

 

 

 

 

Interface inheritance


J

which casts this object into a Speakable object. The reason that this cast is required is because an Animal does not necessarily have a speak() method. A speak() method is not defined in the Animal class. How- ever, the Cat subclass of Animal does implement a sleep() method as part of its Speakable interface. Therefore, in order to invoke speak() on an object from one of the Animal subclasses, the object must actually be a Speakable and we must perform the cast as shown here.

This illustrates, by the way, that a Cat, by virtue of extending the Animal class and implementing the Speakable interface, is both an Animal and a Speakable. In general, a class that implements an in- terface, has that interface as one of its types. Interface implementation is

itself a form of inheritance. A Java class can be a direct subclass of only one superclass. But it can implement any number of interfaces.

Given these definitions of the Cow and Cat subclasses, the following code segment will produce the same results as in the previous section.

 

,,

 

 

 

J

Although the design is different, both approaches produce the same re- sult. We will put off, for now, the question of how one decides whether

 

to use an abstract method or a Java interface. We will get to this question when we design the TwoPlayerGame class hierarchy later in this chapter.