Activity 8.3.1.
Run the following code. Can you change what the cow and cat say?
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.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.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
}
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
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.abstract
method must be declared an abstract class.abstract
class cannot be instantiated. It must be subclassed.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
.abstract
even it contains no abstract
methods. It could, for example, contain instance variables that are common to all its subclasses.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.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 subclassesb ut it would also be accessible to every other class, a violation of the information hiding principle.main()
method: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
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.”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.Animal
hierarchy. We can define and use completely new Animal
subclasses without redefining or recompiling the rest of the classes in the hierarchy. Java’s dynamic binding mechanism enables the Animal.toString()
method to determine the type of Animal
at run time subclass so that it will call the appropriate speak()
method for that type of Animal
.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";
}
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.talk()
method would produce more or less the same result as our polymorphic approach. 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
hierarchy without rewriting and recompiling the talk()
method. Imagine how unwieldy this would become if we want to add many different animals.Animal
subclass named Pig
, which goes “oink.”talk()
method to incorporate the Pig
class.final
variables).ActionListener
interface in Chapter 4.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 it implements an interface.speak()
as an abstract method within the Animal
superclass, we will define it as an abstract method in the Speakable
interface (Fig. Figure 8.3.3).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.(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.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";
}
}
speak()
method.Animal.toString()
class((Speakable)this).speak();
this
object into a Speakable
object. The cast is required because the Animal
class does not have a sleep()
method. Therefore, in order to invoke speak()
on an object from one of the Animal
subclasses, the object must actually be a Speakable
.Cat
, by virtue of extending the Animal
class and implementing the Speakable
interface, is both an Animal
and a Speakable
.Cow
and Cat
subclasses, the following code segment will produce the same results as in the previous 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
Pig
to the hierarchy using the interface implementation.TwoPlayerGame
class hierarchy later in this chapter.