Section 8.3. Abstract Classes, Interfaces, and Polymorphism


[Page 359 (continued)]

8.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 preceding section we saw examples of the first type of polymorphism. 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.


[Page 360]

8.3.1. Implementing an Abstract Method

An important feature of polymorphism is the ability to invoke a polymorphic 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 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 extensiblethat 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.

Extensibility


Figure 8.5 provides a summary of the design we will implement. The Animal class is an abstract class. That is why its name is italicized in the UML diagram. This class is abstract because its speak() method is an abstract method, namely, 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:

public abstract class Animal {     protected String kind;                                // Cow, pig, cat, etc.     public Animal()  {  }     public String toString() {         return "I am a " + kind + " and I go " + speak();     }     public abstract String speak();                       // Abstract method } 


Figure 8.5. The Animal class hierarchy.



[Page 361]

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:

Animal animal = new Animal(); // Error: Animal is abstract 


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.

Rules for abstract classes


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 implements 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 dynamic binding mechanism is the reason this works in Java. 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 case the subclass extends the Animal class and provides its own constructor and its own implementation of the speak() method. Note that in their respective constructors, we can refer to the kind instance variable, which is inherited from the Animal class. By declaring kind as a protected variable, it is inherited by all Animal subclasses but hidden 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.


[Page 362]
Figure 8.6. Two Animal subclasses.
(This item is displayed on page 361 in the print version)

public class Cat extends Animal {     public Cat() {         kind = "cat";     }     public String speak() {         return "meow";     } } public class Cow extends Animal {     public Cow() {         kind = "cow";     }     public String speak() {         return "moo";     } } 

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

Animal animal = new Cow(); System.out.println(animal.toString()); // A cow goes moo animal = new Cat(); System.out.println(animal.toString()); // A cat goes meow 


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 runtime in each case. The invocation of the abstract speak() method in the Animal's toString() method is a second form of polymorphism.

What is the advantage of polymorphism here? The main advantage is the extensibility 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 subclass will be executing its speak() method. The toString() method will work correctly for any subclass of Animal because every nonabstract subclass of Animal must implement the speak() method.

Advantage of polymorphism


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:

public String talk(Animal a) {   if (a instanceof Cow)      return "I am a " + kind + " and I go " + a.moo();   else if (a instanceof Cat)      return "I am a " + kind + " and I go " + a.meow();   else      return "I don't know what I am"; } 


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 gomoo". However, with this design, it is not possible to extend the Animal hierarchy without rewriting and recompiling the talk() method.

Extensibility


Thus, one of the chief advantages of using polymorphism is the great flexibility and extensibility it affords. We can define new Animal subclasses and define their speak() methods. These will all work with the toString() method in the Animal class without any need to revise that method.


[Page 363]

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 nonabstract Animal subclass must implement the speak() method. This lends a great degree of predictability to the subclasses in the hierarchy, making it easier to use them in applications.

Self-Study Exercises

Exercise 8.7

Following the examples in this section, define an Animal subclass named Pig that goes "oink."

Exercise 8.8

Show how you would have to modify the talk() method defined above to incorporate the Pig class.

8.3.2. 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 definitions and constant (final) variables. An interface cannot contain instance variables. We have already seen interfaces, as when we encountered the ActionListener interface in Chapter 4.

Java interface


The designer of an interface specifies what methods will be implemented 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 interface and from an abstract superclass is that a subclass extends an abstract superclass but implements an interface.

Java's interface mechanism gives us another way to design polymorphic 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).

Figure 8.7. Defining and using the Speakable interface.

public interface Speakable {     public String speak(); } public class Animal {     protected String kind;                           // Cow, pig, cat, etc.     public Animal() { }     public String toString() {         return "I am a " + kind + " and I go " + ((Speakable)this).speak();     } } 

Note the differences between this definition of Animal and the previous definition. This version no longer contains the abstract speak() method. Therefore, the class itself is not an abstract class. However, because the speak() method is not declared in this class, we cannot call the speak() method in the toString() method unless we cast this object into a Speakable object.


[Page 364]

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 object 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.

Cast operation


Given these definitions, Animal subclasses will now extend the Animal class and implement the Speakable interface:

public class Cat extends Animal implements Speakable {     public Cat() { kind = "cat"; }     public String speak() { return "meow"; } } public class Cow extends Animal implements Speakable {     public Cow() { kind = "cow"; }     public String speak() { return "moo"; } } 


To implement a Java interface, one must provide a method implementation 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 toString() method in the Animal class

((Speakable)this).speak(); 


which casts this object into a Speakable object. This cast is required because an Animal does not necessarily have a speak() method. A speak() method is not defined in the Animal class. However, 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 interface 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.

Interface inheritance


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

Animal animal = new Cow(); System.out.println(animal.toString());  // A cow goes moo animal = new Cat(); System.out.println(animal.toString());  // A cat goes meow 


Although the design is different, both approaches produce the same result. 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.




Java, Java, Java(c) Object-Orienting Problem Solving
Java, Java, Java, Object-Oriented Problem Solving (3rd Edition)
ISBN: 0131474340
EAN: 2147483647
Year: 2005
Pages: 275

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net