The Artima Developer Community
Sponsored Link

Objects and Java by Bill Venners
Chapter 7:
Polymorphism and Interfaces

Advertisement

There are two good reasons to learn the meaning of polymorphism. First, using such a fancy word in casual conversation makes you sound intelligent. Second, polymorphism provides one of the most useful programming techniques of the object-oriented paradigm. Polymorphism, which etymologically means "many forms," is the ability to treat an object of any subclass of a base class as if it were an object of the base class. A base class has, therefore, many forms: the base class itself, and any of its subclasses.

If you need to write code that deals with a family of types, the code can ignore type-specific details and just interact with the base type of the family. Even though the code thinks it is sending messages to an object of the base class, the object's class could actually be the base class or any one of its subclasses. This makes your code easier for you to write and easier for others to understand. It also makes your code extensible, because other subclasses could be added later to the family of types, and objects of those new subclasses would also work with the existing code.

To see how to use polymorphism in a Java program, consider the family of types shown in Figure 8-1. To use an object of type Liquid, you must create a Liquid object with new and store the returned reference in a variable:

Liquid myFavoriteBeverage = new Liquid();
The myFavoriteBeverage variable holds a reference to a Liquid object. This is a sensible arrangement; however, there is another possibility brought to you courtesy of polymorphism. Because of polymorphism, you can assign a reference to any object that is-a Liquid to a variable of type Liquid. So, assuming the inheritance hierarchy shown in Figure 8-1, either of the following assignments will also work:
Liquid myFavoriteBeverage = new Coffee();
// or...
Liquid myFavoriteBeverage = new Milk();
Therefore, you can sprinkle some polymorphism in your Java program simply by using a variable with a base type to hold a reference to an object of a derived type.



Figure 8-1. The Liquid Family

To get the full benefit of polymorphism in your programs, however, you'll need to go further. To fully realize the wonders of polymorphism, you must send a message to an object without knowing the actual class of the object. To do this in Java, you just invoke a method defined in a base type on an object referenced by a variable of the base type. As you saw above, the object referred to by a base class reference might be of the base class or any of its subclasses. Therefore, when you write the code to invoke the method, you don't necessarily know the actual class of the object. Likewise, when you compile the code, the compiler doesn't necessarily know the actual class of the object. At run-time, the Java Virtual Machine determines the actual class of the object each time the method invocation is requested by your program. Based on this information, the Java Virtual Machine invokes the method implementation belonging to the object's actual class. Letting the Java Virtual Machine determine which method implementation to invoke, based on the actual class of the object, is how you realize the full power of polymorphism in your programs.

As an example, consider the family of types for Liquid shown in Figure 8-1. Assume the base class, Liquid, defines a method swirl() which takes a boolean parameter clockwise. When swirl() is invoked on a Liquid object, the object simulates a swirling motion. If the clockwise parameter is true, the Liquid object swirls clockwise. Otherwise, the Liquid object swirls counterclockwise. Assume also that the subclasses Coffee and Milk override the default implementation of swirl() to account for the unique viscosity of those specific kinds of Liquid. Milk, for instance, might swirl more slowly because it is thicker. This arrangement can be expressed in Java code as follows:

// NEED TO ADD add() TO Liquid
// In Source Packet in file inherit/ex2/Liquid.java
class Liquid {

    void swirl(boolean clockwise) {

        // Implement the default swirling behavior for liquids
        System.out.println("Swirling Liquid");
    }
}

// In Source Packet in file inherit/ex2/Coffee.java
class Coffee extends Liquid {

    void swirl(boolean clockwise) {

        // Simulate the peculiar swirling behavior exhibited
        // by Coffee
        System.out.println("Swirling Coffee");
    }
}

// In Source Packet in file inherit/ex2/Milk.java
class Milk extends Liquid {

    void swirl(boolean clockwise) {

        // Model milk's manner of swirling
        System.out.println("Swirling Milk");
    }
}

To carry the example further, take another look at the addLiquid() method of the Cup family of types shown in Figure 8-1. Assume class Cup defines an addLiquid()method that you have overridden in class CoffeeCup, much like the example on the right hand side of Figure 7-2. In the right hand version of Figure 7-2, class CoffeeCup overrides addLiquid() so that the liquid can be made to swirl counterclockwise as it is added to the cup. Now that you have a Liquid class with an actual swirl() method, you could implement the addLiquid() method in CoffeeCup as follows:

// In Source Packet in file inherit/ex2/CoffeeCup.java
class CoffeeCup {

      private Liquid innerLiquid;

      void addLiquid(Liquid liq) {

            innerLiquid.add(liq);

            // Swirl counterclockwise
            innerLiquid.swirl(false);
      }
}

Given the above definition of CoffeeCup, you can reap the benefit of polymorphism by invoking addLiquid() with different kinds of liquid:

// In Source Packet in file inherit/ex2/Example2.java
class Example2 {

    public static void main(String[] args) {

        // First you need a coffee cup
        CoffeeCup myCup = new CoffeeCup();

        // Next you need various kinds of liquid
        Liquid genericLiquid = new Liquid();
        Coffee coffee = new Coffee();
        Milk milk = new Milk();

        // Now you can add the different liquids to the cup
        myCup.addLiquid(genericLiquid);
        myCup.addLiquid(coffee);
        myCup.addLiquid(milk);
    }
}

Note that the definition of addLiquid() treats the object passed in as a parameter as if it is of type Liquid, yet the code above passes types Coffee and Milk as well as Liquid. The code of the addLiquid() method, therefore, doesn't know the exact class of the object it is being passed. When swirl() is invoked on the object at run-time, the implementation of swirl() that gets executed depends upon the actual class of the object. In the first case, when genericLiquid is passed, Liquid's implementation of swirl() will be executed. When coffee is passed, Coffee's implementation of swirl() will be executed. In the last case, when milk is passed, Milk's implementation of swirl() will be executed.

Therefore, when milk is added to the cup, it will swirl like milk. When coffee is added, it will swirl like coffee. All this is accomplished even though the code of the addLiquid() method does not know the actual class of the object passed. This is the beauty of polymorphism.

Had you not known about polymorphism, you might have designed the Liquid family and the addLiquid() method differently. Instead of taking advantage of polymorphism's ability to figure out which method to call, you could have used a brute force method:

// In Source Packet in file inherit/ex3/UglyCoffeeCup.java
// This version doesn't take advantage of polymorphism.
class UglyCoffeeCup {

    Liquid innerLiquid;

    void addLiquid(Liquid liq) {

        innerLiquid = liq;
        if (liq instanceof Milk) {
            ((Milk) innerLiquid).swirlLikeMilk(false);
        }
        else if (liq instanceof Coffee) {
            ((Coffee) innerLiquid).swirlLikeCoffee(false);
        }
        else {
            innerLiquid.swirlLikeGenericLiquid(false);
        }
    }
}

The creation of if-else constructs like the one shown above are possible in Java because of the instanceof operator, which allows you to check whether an object is an instance of a certain class. Here the instanceof operator is being abused, and the code should be reorganized to use polymorphism. The instanceof operator, and the situations in which it should be used, will be discussed later in this chapter.

The above code is more difficult to read and less extensible than the code (shown earlier) that took full advantage of polymorphism. If later you added a new type to the Liquid family, say Tea, you would have to add another if-else statement to the above code. You would not, however, need to make any changes to the earlier addLiquid() implementation that took advantage of polymorphism. That implementation of addLiquid() would still work, even though you wrote it before you knew about Tea. Whenever you find yourself writing a series of if- else statements, where the condition is a test of type, you should try and see if your program can't be redesigned to take advantage of polymorphism.

Programming down the path of polymorphism implies that you write code that lets an object figure out how it should behave. In the previous example, you told an object to swirl, and expected it to swirl in the manner objects of its class are supposed to swirl. You didn't tell it explicitly whether it should swirl like milk or coffee, you just told it to swirl. If the object was actually milk, you expected it to behave like milk and swirl in a milky way. This attitude towards objects fits well with the object-oriented mindset, because the customary mission of a method is to manipulate the internal data of the object. Objects are supposed to know best how to manipulate their own data, which is the reason data is usually private. Keeping data private gives full responsibility for proper manipulation of the internal state of an object to the object's methods. Polymorphism gives you another way in which you can give objects responsibility for their own behavior--the object's behavior matches its class.

Static versus Dynamic Binding

The underlying mechanism that makes polymorphism possible is dynamic binding. Except for three special cases, all instance methods in Java programs are dynamically bound. The instance method that is invoked at run-time will be determined by the actual class of the object, not by the type of the reference. This differs from C++, in which you must declare an instance method virtual to get polymorphism and dynamic binding. If you don't declare an instance method virtual in a C++ class, you get static binding, in which the method called is determined by the type of the reference, not the class of the object referred to by the reference. Polymorphism plays no role in static binding. In C++, therefore, a programmer can, wielding the power of the virtual keyword, create instance methods that are either statically or dynamically bound. In Java, however, "virtual" is not a keyword, and all instance methods, except for the three special cases, are dynamically bound. The three special cases are private methods, instance method invocations with the super keyword, and invocation of instance initialization methods. These cases will be described further later in this chapter.

Static binding also plays a role in class methods (those declared with the static modifier), which are always statically bound. You can redefine a class method in a subclass, just like an instance method; however, a redefined class method will not participate in the rewards of polymorphism and dynamic binding. Because class methods do not operate on specific objects, you don't even need an object to call them. Class methods can be invoked even when no instances of the class exist. Polymorphism requires an object, because it enables a method to be dynamically selected based on the actual class of the object. Thus, polymorphism does not apply to class methods. If you invoke a class method on an object, the implementation of the class method is chosen based not on the class of the object at run-time, but on the type of the object reference at compile-time.

For example, imagine you want to be able to simulate an earth tremor in your cafe, by sending a message to all liquids in the cafe, telling the liquids to gurgle. The clientele of the cafe will know something is happening when, as a result of the pan-liquid gurgle command, they see their coffees and teas producing bubbles and generally sloshing about. One way you could model this in your Java program is by declaring a static method, gurgle(), in the Liquid class:

// In Source Packet in file inherit/ex4/Liquid.java
class Liquid {

    void swirl(boolean clockwise) {
        System.out.println("One Liquid object is swirling.");
    }

    static void gurgle() {
        System.out.println("All Liquid objects are gurgling.");
    }
}

Suppose you also wish to model an unusual kind of earth tremor that affects only milk, but not any other type of liquid. Perhaps the frequency of the tremor matches exactly the resonant frequency of milk, so milk is the only liquid visibly affected. In this case you want to be able to send the gurgle command to all milks, but not to any other liquids in the cafe. You could model this in your program by adding a gurgle() method to the Milk class:

// In Source Packet in file inherit/ex4/Milk.java
class Milk extends Liquid {

    void swirl(boolean clockwise) {
        System.out.println("One Milk object is swirling.");
    }

    static void gurgle() {
        System.out.println("All Milk objects are gurgling.");
    }
}

Armed with the implementations of Liquid and Milk shown above, you are ready to have some fun. Assume you wish to start by simulating an earth tremor that gurgles milk, but leaves all other liquids alone. Given your pleasant experience of polymorphism with instance methods, you might try to accomplish the milk gurgling by code similar to the following:

// In Source Packet in file inherit/ex4/Example4a.java
class Example4a {

    public static void main(String[] args) {

        Liquid liq = new Milk();
        liq.swirl(true);
        liq.gurgle();
    }
}
Unfortunately, the above code will generate the following output:
One Milk object is swirling.
All Liquid objects are gurgling.

The output generated by the above code demonstrates the statically linked nature of class method invocations. In this code you have a reference of type Liquid, but an object of class Milk. When you invoked swirl() on the object, polymorphism came through for you, because Milk's implementation of swirl() was executed. When you invoked gurgle(), however, polymorphism abandoned you, and Liquid's implementation of gurgle() was executed. The implementation of gurgle() to invoke was determined statically at compile-time based on the reference's type (Liquid), not dynamically at run-time based on the object's class (Milk).

One way to solve the problem is to make sure you invoke gurgle() on a Milk object being referred to by a reference of type Milk, as in:

// In Source Packet in file inherit/ex4/Example4b.java
class Example4b {

    public static void main(String[] args) {

        Milk m = new Milk();
        m.swirl(true);
        m.gurgle();
    }
}
This code generates the desired effect. One Milk object is swirled, and all Milk objects are gurgled. The output generated by the above code is:
One Milk object is swirling.
All Milk objects are gurgling.
Yet even though you have gotten the results you want, you still haven't written code that clearly indicates the class-wide nature of gurgle(). Most of the time, the best way to invoke a class method is to use the class name, not a reference to an object of the class. For example, you could rewrite the above code:
// In Source Packet in file inherit/ex4/Example4c.java
class Example4c {

    public static void main(String[] args) {

        Milk m = new Milk();
        m.swirl(true);
        Milk.gurgle();
    }
}
The line Milk.gurgle() more clearly indicates that a class method is being invoked, and that polymorphism is not involved.

Redefining an instance method in a subclass is called "overriding" the instance method, however, redefining a class method is not called "overriding." Instead, it is called hiding the class method. The term "override" is associated with polymorphism, which doesn't apply to class methods. Therefore, you can't override a class method, you can only hide it. For example, the gurgle() method defined in Milk above hides the gurgle() implementation defined in Liquid.

Because an invocation of a statically bound class method on an object looks similar to the invocation of a dynamically bound instance method, you must be careful to always keep in mind the difference. Invoking class methods using the class name, as in Milk.gurgle(), instead of an object reference, as in m.gurgle(), is a good rule of thumb to help clarify your code.

The this Reference

Point out using this for this.attr = attr, and mention this is a kind of hiding. Talk about hiding in general, and make another request for private data.

The super Reference

Here talk about this, super, and ((SuperClass) var).memberName. Yes. This is where I can mention the static binding of fields, because I show it with the (()) example. Whereas you could invoke a virtual method in a superclass in C++ by using the scope resolution operator, in Java you can't access any instance method that the current class overrides in any superclass other than your direct superclass. You can do it with a field, whether its an instance or class variable, and with a class method simply by casting the this reference to a superclass type.

Any field you declare private can't be hidden. If you favor private data in your class designs, field hiding should be rare. You may occasionally encounter hiding with constant fields, which are often declared public. You may also encounter field hiding when you use libraries that declare public fields.

One other justification for keeping data private is that fields are accessed not based on the class of the object, but on the type of the reference. If a subclass hides a public instance variable of its superclass, then

[YIKES, DON'T I NEED TO TALK ABOUT STUFF FROM PAGE 61 OF JPL BOOK? FIELDS ARE ACCESSED NOT BASED ON THE CLASS OF OBJECT, BUT THE TYPE OF THE REFERENCE.]

Adding New Fields and Methods to a Subclass

The techniques discussed so far yield families of types in which all classes in the family have the precisely same interface. A subclass can differentiate itself from all the other classes in the family by overriding methods inherited from its direct superclass. In this very object-oriented scheme, a class that belongs to a family of types expresses its uniqueness not by the interface it presents to the world, but by the implementation underneath the interface. All the classes in a family might have a swirl() method, for example, but each individual class might swirl in its own unique way. This manner of modeling families of types is very expressive and allows you to take advantage of polymorphism, but can sometimes restrict your ability to model the specific nature of a subclass. Sometimes you may want a subclass to accept messages that its superclass does not accept. To do this you must extend the inherited interface. You must add to the subclass new methods that did not exist in the superclass.

Java allows you to define methods that enable a subclass to receive messages that would normally be accepted only by the subclass, and not by a more general superclass. This muddies the object-oriented metaphor a bit, because even though you can still substitute a subclass wherever a superclass is required, the new methods you added to the subclass aren't accessible when you are treating the subclass as if it is a superclass. For this reason, you will usually want to first attempt to design families of types in which subclasses contain only methods that override methods inherited from superclasses. Sometimes, however, you will feel the need to add new methods to subclasses. In those situations you must just live with the inability to invoke one of the new methods when you have a reference to an object of the base type. You will only be able to invoke the new methods when you have an explicit reference to the subclass type in which the new methods are defined.

As an example, consider again the family of liquids. Up to this point you have been introduced to four members of the liquid family, Liquid, the base class, and subclass siblings Coffee, Milk, and Tea. Each of the subclasses overrides the default implementation of swirl(), defined in base class Liquid. So far every class in the family has the same interface, which is composed of just two methods: swirl() and gurgle(). Now suppose you want to be able to invoke a method on an object of class Tea that causes the object to inspect itself, and from the configuration of tea leaves floating in itself, describe the future of the person who is drinking the tea.

The ability to predict a person's future from the tea leaves left after they drink a cup of tea is not a general property of liquids. It is a property only of tea. If you added a readFuture() method to base class Liquid, that would imply that one can see the future by peering into any liquid. But this is not true of coffee. It is not true of milk. (It is probably not true of tea either, but for the sake of this illustration, assume it is.) Therefore, the best way to model this in your design is to add a readFuture() method to the Tea class only:

// In Source Packet in file inherit/ex5/Liquid.java
class Liquid {

    void swirl(boolean clockwise) {
        System.out.println("Liquid Swirling");
    }
}

// In Source Packet in file inherit/ex5/Tea.java
class Tea extends Liquid {

    void swirl(boolean clockwise) {
        System.out.println("Tea Swirling");
    }

    void readFuture() {
        System.out.println("Reading the future...");
    }
}

// In Source Packet in file inherit/ex5/Coffee.java
class Coffee extends Liquid {

    void swirl(boolean clockwise) {
        System.out.println("Coffee Swirling");
    }
}

// In Source Packet in file inherit/ex5/Milk.java
class Milk extends Liquid {

    void swirl(boolean clockwise) {
        System.out.println("Milk Swirling");
    }
}

Given the design represented by the code above, you will not be able to call readFuture() if you have a reference to an object of type Liquid, even if the actual object being referenced is type Tea. As demonstrated below, only if you have a reference of type Tea can you invoke the readFuture() method:

// In Source Packet in file inherit/ex5/Example5a.java
class Example5a {

    public static void main(String[] args) {

        // Create a Tea reference and a Tea object
        Tea tea = new Tea();

        // Create a Liquid reference and, in the spirit of
        // polymorphism, assign to it the same Tea object
        Liquid liq = tea;

        // Ask the tea object to read the future of its drinker
        tea.readFuture();

        // Attempt to ask the same tea object to read the future
        // again, but this time via the reference to Liquid.
        liq.readFuture();   // THIS WON'T COMPILE.
    }
}

The example above demonstrates the consequence of adding new methods to subclasses. The new methods can be called only when the type of the reference is the subclass. In this situation, however, it is a reasonable way to model the fortune telling behavior of tea, and the consequences of the design are acceptable.

When to Use instanceof

The fortune-telling behavior of Tea illustrates a situation in which you might want to use instanceof. If you have a reference to a Liquid and you want to swirl the liquid clockwise, you can use polymorphism because all liquids swirl:

// In Source Packet in file inherit/ex5/Example5b.java
class Example5b {

    public static void doSomethingWithALiquid(Liquid liq) {

        liq.swirl(true);
    }

    public static void main(String[] args) {

        // Create a Tea reference and a Tea object
        Tea tea = new Tea();
        doSomethingWithALiquid(tea);
    }
}
If you also want, if the liquid actually is tea, to read the drinker's future, you can't use polymorphism, because not all liquids can predict the future:
// In Source Packet in file inherit/ex5/Example5c.java
class Example5c {

    public static void doSomethingWithALiquid(Liquid liq) {

        liq.swirl(true);
        liq.readFuture();   // THIS WON'T COMPILE
    }

    public static void main(String[] args) {

        // Create a Tea reference and a Tea object
        Tea tea = new Tea();
        doSomethingWithALiquid(tea);
    }
}
In this case, you must use instanceof to determine whether the object really is tea, and if so, downcast the reference to type Tea, and invoke readFuture() on that:
// In Source Packet in file inherit/ex5/Example5d.java
class Example5d {

    public static void doSomethingWithALiquid(Liquid liq) {

        liq.swirl(true);
        if (liq instanceof Tea) {
            Tea tea = (Tea) liq;
            tea.readFuture();
        }
    }

    public static void main(String[] args) {

        // Create a Tea reference and a Tea object
        Tea tea = new Tea();
        doSomethingWithALiquid(tea);
    }
}
The process of converting the Liquid reference into a Tea reference is called "downcasting" because you are casting the reference "down" the inheritance hierarchy, from Liquid to Tea. This illustrates the kind of situation in which you should use instanceof. You have a base type reference, and if the object referred to by the base type reference really is a certain subclass, you want to invoke a method that only exists in that subclass.

Incidentally, Java ensures type-safety at run-time. If, for example, your program attempts at run-time to downcast to Tea a Liquid reference that actually refers to a Milk object, the Java Virtual Machine will throw a ClassCastException. Each time a cast is performed, the actual class of the object is checked to make sure the cast is valid.

Behind the Scenes

Statically Bound Instance Methods

The three special cases, mentioned above, in which Java performs static binding on instance methods are: o private methods o methods invoked with the super keyword o instance initialization methods

When you invoke a private method from another method, both methods must be defined in the same class. Although a method of the same signature as a private method can be declared in a subclass, a private method can't be overridden by a subclass. When you invoke a private method, the Java compiler knows precisely which class contains the method to invoke, because it must by definition be in the same class. Static binding is used so that the private method is invoked independent of the actual class of the object at run-time.

The super keyword, which will be described in detail later in this chapter, allows you to access a superclass's methods and fields from a subclass, even if they are overridden or hidden in the subclass. In the case of instance methods, static binding must be used. If a method is overridden in a subclass, dynamic binding would cause the subclass's version of the method to be invoked rather than the superclass's version. As with invocation of a private method, the compiler knows precisely which class contains the method to invoke when it is invoked with the super keyword. Static binding allows a superclass's version of an instance method to be invoked independent of the actual class of the object at run-time.

The Java compiler creates one instance initialization method for each constructor in the source for a class. This special kind of instance method is invoked only when an object is created. Like private methods and methods invoked with super, instance initialization methods are invoked using static binding. The details of this special kind of method will be described in Chapter 13.

For more information on static binding of instance methods, see the explanation of the invokespecial instruction in Chapter 25.

Interfaces

As illustrated in the previous chapter, one of the most important benefits of class extension is that you can take advantage of polymorphism. In an inheritance hierarchy, if class CoffeeCup extends class Cup, you can treat a CoffeeCup object as if it were a Cup object. Sometimes, however, it is difficult to get the polymorphism you want from the singly-inherited hierarchies you can build with class extension. To help you get more polymorphism than you can easily get with single- inheritance, Java supports a restricted form of multiple inheritance through a construct called the "interface." This chapter will discuss the motivation and the mechanics of the Java interface.

The Limitations of Single Inheritance

To reap the benefits of polymorphism through class extension, you must build a family of classes. In Java terminology, both classes and interfaces are "types." When you declare an interface, as when you declare a class, you establish a new type. In the remainder of this book, "type" will be used to refer to either classes or interfaces. Here, a "family of classes" is simply a family of types in which all the types are classes (none are interfaces). Thus, a family of classes is a group of related classes with a single base class from which all other classes in the family descend. Since every class in Java descends from Object, all Java classes are members of the Object family; however, you can still look at individual areas of an inheritance hierarchy as individual "families of classes." For example, class Cup and all its subclasses, as shown in Figure 4-1, form the Cup family. [bv: I believe I covered this already in a previous chapter.]


Figure 4-1. The Cup family

Given a family of classes, polymorphism allows you to treat a subclass object as if it were a superclass object. For example, imagine you wanted to create a single method that could wash any kind of cup in your virtual cafe. You could declare a public method named wash() in the base class of the Cup family:

// In Source Packet in file interface/ex1/Cup.java
class Cup {
    public void wash() {
        System.out.println("Washing a Cup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex1/CoffeeCup.java
class CoffeeCup extends Cup {
    public void wash() {
        System.out.println("Washing a CoffeeCup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex1/CoffeeMug.java
class CoffeeMug extends CoffeeCup {
    public void wash() {
        System.out.println("Washing a CoffeeMug.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex1/EspressoCup.java
class EspressoCup extends CoffeeCup {
    public void wash() {
        System.out.println("Washing an EspressoCup.");
        // ...
	}
    //...
}

Given this family of types, you could define a method that takes a Cup reference as follows:

// In Source Packet in file interface/ex1/VirtualCafe.java
class VirtualCafe {
    public static void prepareACup(Cup cup) {
        //...
        cup.wash();
        //...
    }
    //...
}

Using polymorphism, you could pass to the method a reference to any object that is-a Cup:

// In Source Packet in file interface/ex1/Example1.java
class Example1 {
    public static void main(String[] args) {

        Cup c = new Cup();
        CoffeeCup cc = new CoffeeCup();
        CoffeeMug cm = new CoffeeMug();
        EspressoCup ec = new EspressoCup();
        VirtualCafe.prepareACup(c);
        VirtualCafe.prepareACup(cc);
        VirtualCafe.prepareACup(cm);
        VirtualCafe.prepareACup(ec);
    }
}

Here you have all the benefits of polymorphism. The prepareACup() method can invoke wash() on many different objects, but it doesn't need to use instanceof. As a consequence, the code is easier to read and change. If later, you wanted to add class TeaCup to your program and wash TeaCup objects with prepareACup(), you only need to make TeaCup a subclass of Cup. You don't need to change the prepareACup() method itself.

This all works fine, but what if you wanted to wash a greater variety of objects with a single method? What if you wanted to have a method that can wash any kind of object for which washing makes sense-- any "washable" object? For example, besides washing cups, you might also want to wash a window, wash your car, or wash a dog. Since these objects don't seem to fit into the same family, you might end up using instanceof instead of polymorphism. For example, consider these classes:

// In Source Packet in file interface/ex2/Window.java
class Window {
    public void wash() {
        System.out.println("Washing a Window.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex2/Cup.java
class Cup {
    public void wash() {
        System.out.println("Washing a Cup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex2/CoffeeCup.java
class CoffeeCup extends Cup {
    public void wash() {
        System.out.println("Washing a CoffeeCup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex2/CoffeeMug.java
class CoffeeMug extends CoffeeCup {
    public void wash() {
        System.out.println("Washing a CoffeeMug.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex2/EspressoCup.java
class EspressoCup extends CoffeeCup {
    public void wash() {
        System.out.println("Washing an EspressoCup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex2/Car.java
class Car {
    public void wash() {
        System.out.println("Washing a Car.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex2/Dog.java
class Dog {
    public void wash() {
        System.out.println("Washing a Dog.");
        // ...
    }
    //...
}

Here, instead of having one family of classes for washable objects, you have four separate families: cups, dogs, cars, and windows. Each washable object in each family declares wash(), but because there is no common base class that declares wash(), you can't use polymorphism. To create a method that can wash any of these kinds of objects, your method would have to use instanceof:

// In Source Packet in file interface/ex2/Cleaner.java
class Cleaner {

    // (This doesn't use polymorphism)
    public static void cleanAnObject(Object obj) {

        // Perform any necessary processing of the
        // object before washing...

        // Wash the object
        if (obj instanceof Cup) {
            // (Here you are using polymorphism, but just
            // within the Cup family.)
            ((Cup) obj).wash();
        }
        else if (obj instanceof Dog) {
            ((Dog) obj).wash();
        }
        else if (obj instanceof Window) {
            ((Window) obj).wash();
        }
        else if (obj instanceof Car) {
            ((Car) obj).wash();
        }
        // Else the object doesn't get washed

        // Perform other processing on the object to
        // complete the cleaning process...
    }
}

This cleanAnObject()method will work, but it doesn't participate in the benefits of polymorphism. Most significantly, this code is less flexible than if it were able to take advantage of polymorphism. With the above code, you'd have to add another instanceof check if you want to add another kind of washable object, say Bicycle, to your program.

To improve this situation, you might decide to give cups, cars, windows, and dogs a common base class that declares the wash() method. This would allow you to get the full benefit of polymorphism in the cleanAnObject() method. Here, the four families--cups, cars, windows, and dogs--are combined into the WashableObject family:

// In Source Packet in file interface/ex3/WashableObject.java
abstract class WashableObject {
    public abstract void wash();
}

// In Source Packet in file interface/ex3/Window.java
class Window extends WashableObject {
    public void wash() {
        System.out.println("Washing a Window.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex3/Cup.java
class Cup extends WashableObject {
    public void wash() {
        System.out.println("Washing a Cup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex3/CoffeeCup.java
class CoffeeCup extends Cup {
    public void wash() {
        System.out.println("Washing a CoffeeCup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex3/CoffeeMug.java
class CoffeeMug extends CoffeeCup {
    public void wash() {
        System.out.println("Washing a CoffeeMug.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex3/EspressoCup.java
class EspressoCup extends CoffeeCup {
    public void wash() {
        System.out.println("Washing an EspressoCup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex3/Car.java
class Car extends WashableObject {
    public void wash() {
        System.out.println("Washing a Car.");
        //...
    }
    //...
}

// In Source Packet in file interface/ex3/Dog.java
class Dog extends WashableObject {
    public void wash() {
        System.out.println("Washing a Dog.");
        //...
    }
    //...
}

Given this WashableObject family, which is shown graphically in Figure 4-2, you can create a single method that uses polymorphism to wash any kind of washable object:

// In Source Packet in file interface/ex3/Cleaner.java
class Cleaner {
    public static void cleanAnObject(WashableObject wo) {
        //...
        wo.wash();
        //...
    }
}


Figure 4-2. The WashableObject family

As this example demonstrates, it is possible to fit cups, windows, cars, and dogs all into the same family of classes; however, the resulting family, WashableObject, is not very intuitive and not very flexible.

As an example of this family's inflexibility, imagine that later you decide you want Dog to descend from Animal. You would have to make Animal descend from WashableObject. But what if, as is shown in Figure 4-4, you declared Cat and Fish as subclasses of Animal too? Is a Cat washable? Potentially, but you'd best let the Cat take care of that itself. And how do you wash a Fish? Although each of these washings are possible to imagine, they may not be a behavior you intend a Cat or Fish to exhibit. You are making Cats and Fish endure washing, when all you really want to do is keep the Dog clean. Given that you want Dogs to both to descend from Animal and be washable, however, you need to either make every Animal a WashableObject or every WashableObject an Animal. Here is the code for the Animal family:

// In Source Packet in file interface/ex5/WashableObject.java
abstract class WashableObject {
    public abstract void wash();
}

// In Source Packet in file interface/ex5/Animal.java
class Animal extends WashableObject {
    public void wash() {
        System.out.println("Washing an Animal.");
        //...
    }
    //...
}

// In Source Packet in file interface/ex5/Dog.java
class Dog extends Animal {
    public void wash() {
        System.out.println("Washing a Dog.");
        //...
    }
    //...
}

// In Source Packet in file interface/ex5/Cat.java
class Cat extends Animal {
    public void wash() {
        System.out.println("Washing a Cat.");
        //...
    }
    //...
}

// In Source Packet in file interface/ex5/Fish.java
class Fish extends Animal {
    public void wash() {
        System.out.println("Washing a Fish.");
        //...
    }
    //...
}


Figure 4-4. Nervous cats and puzzled fish

The problem here is that you are using class extension not to model specialization of objects in the problem domain, but simply to get at polymorphism. In your problem domain, is it true that a Cup is-a WashableObject? What exactly is a washable object? What does one look like? Washable is an adjective, not a noun. It describes a behavior exhibited by objects, not an object itself. To get the benefits of polymorphism, you insert WashableObject into the inheritance hierarchy, but it doesn't fit very well.

Multiple Inheritance with Interfaces

Although class extension only allows single inheritance (each class can have at most one direct superclass), Java offers a special variation of multiple inheritance through the "interface." An interface is like an abstract class that has only public abstract methods and public static final fields. An interface (the Java construct) represents a pure interface (the object-oriented concept); it has no implementation.

Interfaces in Java allow you to get the benefits of polymorphism without requiring you to build a singly-inherited family of classes. Although a class can extend only one other class, it can "implement" multiple interfaces. Interfaces allow you to use families of classes to model what objects are (such as cups or animals) rather than what you plan to do with them (such as wash them). You can design a family of classes for cups, another for animals (including dogs), one for vehicles (including cars), one for parts of a building (including windows). Then each washable class can implement the Washable interface. Here is a potential declaration of the interface:

// In Source Packet in file interface/ex6/Washable.java
interface Washable {

    int IMPERVIOUS = 0;
    int RESISTENT = 1;
    int FRAGILE = 2;
    int EXPLOSIVE = 3;

    /**
    * returns true if the object needs to be washed
    */
    boolean needsWashing();

    /**
    * washes the object
    */
    void wash();
}

The methods declared in this interface are not explicitly declared public and abstract, because they are public and abstract by default. Likewise, the constants in Washable are not declared public, static, and final, because they are so by default.

A class Cup could implement the Washable interface as follows:

// In Source Packet in file interface/ex6/Cup.java
class Cup extends Object implements Washable {

	public int getLevelOfFragility() {
		return Washable.FRAGILE;
	}

	public boolean needsWashing() {
		// No implementation yet...
		// hard-code a return value so it will compile
		return true;
	}

	public void wash() {
	    System.out.println("Washing a Cup.");
	    //...
	}
	//...
}

// In Source Packet in file interface/ex6/CoffeeCup.java
class CoffeeCup extends Cup {

	public void wash() {
	    System.out.println("Washing a CoffeeCup.");
	    //...
	}
	//...
}

Class Cup declares that it implements interface Washable, so it must implement each method contained in that interface. If it doesn't, it must declare itself as abstract. (If class Cup didn't implement the methods contained in the interfaces and didn't declare itself abstract, it wouldn't compile.) In this case, it implements all methods declared in Washable. Class CoffeeCup, which extends class Cup, can either inherit or override Cup's implementation of the methods defined in Washable and Breakable. Figure 4-5 shows an inheritance hierarchy for this version of class Cup and CoffeeCup. [bv: Explain the UML diagram for interfaces.] Note that interfaces do not descend from class Object.


Figure 4-5. An inheritance hierarchy that includes interfaces

Although interfaces (like abstract classes) cannot be instantiated by themselves, you can create a variable to hold a reference to an interface type:

// In Source Packet in file interface/ex6/Example6a.java
class Example6a {
    public static void main(String[] args) {

        // OK to declare a variable as an interface type
        Washable wa;

        // Can't instantiate an interface by itself.
        wa = new Washable(); // THIS WON'T COMPILE
    }
}
Given an object variable of an interface type (such as Washable wa), you can assign a reference to an object of a class that implements the interface (such as class CoffeeCup):
// In Source Packet in file interface/ex6/Example6b.java
class Example6b {
    public static void main(String[] args) {

        Washable wa = new CoffeeCup();
        wa.wash();
    }
}

Thus, you can upcast a CoffeeCup reference not only to a Cup or an Object reference, but also to a Washable reference as well. On an interface reference such as wa above, you can invoke any method declared in the interface, as wash() was in this example.

The Interface Solution

Given the Washable interface, you could overcome the difficulties encountered earlier in writing cleanAnObject(). Here is how you could declare the classes and the interface:

// In Source Packet in file interface/ex7/Washable.java
interface Washable {
    void wash();
}

// In Source Packet in file interface/ex7/Window.java
class Window implements Washable {
    public void wash() {
        System.out.println("Washing a Window.");
        //...
    }
    //...
}

// In Source Packet in file interface/ex7/Cup.java
class Cup implements Washable {
    public void wash() {
        System.out.println("Washing a Cup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex7/CoffeeCup.java
class CoffeeCup extends Cup {
    public void wash() {
        System.out.println("Washing a CoffeeCup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex7/CoffeeMug.java
class CoffeeMug extends CoffeeCup {
    public void wash() {
        System.out.println("Washing a CoffeeMug.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex7/EspressoCup.java
class EspressoCup extends CoffeeCup {
    public void wash() {
        System.out.println("Washing an EspressoCup.");
        // ...
    }
    //...
}

// In Source Packet in file interface/ex7/Car.java
class Car implements Washable {
    public void wash() {
        System.out.println("Washing a Car.");
        //...
    }
    //...
}

// In Source Packet in file interface/ex7/Animal.java
class Animal {
    //...
}

// In Source Packet in file interface/ex7/Dog.java
class Dog extends Animal implements Washable {
    public void wash() {
        System.out.println("Washing a Dog.");
        //...
    }
    //...
}

// In Source Packet in file interface/ex7/Cat.java
class Cat extends Animal {
    //...
}

// In Source Packet in file interface/ex7/Fish.java
class Fish extends Animal {
    //...
}

The inheritance hierarchy for these classes is shown in Figure 4-6. Given these definitions for cups, animals, windows, and cars, you could once again get the benefits of polymorphism when writing method cleanAnObject():

// In Source Packet in file interface/ex7/Cleaner.java
class Cleaner {
    public static void cleanAnObject(Washable washMe) {
        //...
        washMe.wash();
        //...
    }
}


Figure 4-6. The interface solution

Interfaces allow you to get the benefits of polymorphism without requiring that you fit everything into one singly-inherited family of classes. In the examples above, the Washable interface defines a standard way to do washing, and any class can implement it. Because of interfaces, you can use class extension to model what objects are. You can use interface implementation to get polymorphism based purely on what an object does. Any desired polymorphism that class extension doesn't produce, you can get with interface implementation.

Implementing Multiple Interfaces

[bv: have one example of this.]

Interfaces Extending Interfaces

Similar to classes, you can build up inheritance hierarchies of interfaces by using the extends keyword, as in:

// In Source Packet in file interface/ex8/Washable.java
interface Washable {
	void wash();
}

// In Source Packet in file interface/ex8/Soakable.java
interface Soakable extends Washable {
	void soak();
}

In this example, interface Soakable extends interface Washable. Consequently, Soakable inherits all the members of Washable. A class that implements Soakable must provide bodies for all the methods declared in or inherited by Soakable, wash() and soak(), or be declared abstract. Note that only interfaces can "extend" other interfaces. Classes can't extend interfaces, they can only implement interfaces.

Soakable inherits member wash()from its superinterface. Similar to "subclass" and "superclass," interfaces in an inheritance hierarchy can be called "subinterface" and "superinterface." To refer to either classes or interfaces, you can say "subtype" and "supertype." As with subclass and superclass, you can use "direct" to indicate that a type or interface is a direct descendant or ancestor of another type or interface, as in "direct superinterface" or "direct subtype."

To extend the previous example further, here are a few more interfaces:

// In Source Packet in file interface/ex8/Scrubable.java
interface Scrubable extends Washable {
	void scrub();
}

// In Source Packet in file interface/ex8/BubbleBathable.java
interface BubbleBathable extends Soakable, Scrubable {
	void takeABubbleBath();
}

In this example, Washable, Soakable, and Scrubable are all superinterfaces of BubbleBathable. (Note that BubbleBathable extends two direct superinterfaces. Just as classes can implement multiple interfaces, interfaces can extend multiple interfaces.) Classes that implement BubbleBathable must therefore provide bodies for methods declared in Washable, Soakable, Scrubable, and BubbleBathable, or be declared abstract. Figure 4-7 shows the inheritance hierarchy for this family of interfaces.


Figure 4-7. Interfaces can extend other interfaces

Using instanceof with Interfaces

Given a reference to an object, you can find out if a particular interface is a superinterface of that object's class by using instanceof. For example, the washIfPossible() method, shown below, uses instanceof to determine whether an object is a subtype of the Washable interface:

// In Source Packet in file interface/ex9/Example9.java
class Example9 {

    public static void washIfPossible(Object o) {
        if (o instanceof Washable) {
            // Washable is a superinterface of the
            // object's class
            ((Washable) o).wash();
        }
        else {
            System.out.println("Can't wash this.");
        }
    }

    public static void main(String[] args) {
        washIfPossible(new Cup());
        washIfPossible(new CoffeeCup());
        washIfPossible(new CoffeeMug());
        washIfPossible(new EspressoCup());
        washIfPossible(new Car());
        washIfPossible(new Animal());
        washIfPossible(new Dog());
        washIfPossible(new Cat());
        washIfPossible(new Fish());
        washIfPossible(new Window());
    }
}

Alternatively, you can obtain a list of all the interfaces an object's class implements using the java.lang.Class object. This will be described in the chapter on Reflections, Chapter 19. These mechanisms allow you to query an object to find out what methods you can invoke on it, or "what it can do for you."

Name Conflicts

Multiple inheritance brings with it the potential for name conflicts. If, for example, the Soakable and Scrubable interfaces both declare a method named dryOff(), classes that implement both Soakable and Scrubable would inherit dryOff() twice. If dryOff() has different signatures in both interfaces, then the class would inherit two overloaded names, and would have to define implementations for both, or else declare itself as abstract:

// In Source Packet in file interface/ex10/Washable.java
interface Washable {
    void wash();
}

// In Source Packet in file interface/ex10/Soakable.java
interface Soakable extends Washable {
    void soak();
    void dryOff();
}

// In Source Packet in file interface/ex10/Scrubable.java
interface Scrubable extends Washable {
    void scrub();
    void dryOff(boolean withTowel);
}

// In Source Packet in file interface/ex10/Cup.java
class Cup implements Soakable, Scrubable {
    // implement Soakable's dryOff()
    public void dryOff() {
        // ...
    }
    // implement Scrubable's dryOff()
    public void dryOff(boolean withTowel) {
        //...
    }
    public void wash() {
        //...
    }
    public void soak() {
        //...
    }
    public void scrub() {
        //...
    }
    //...
}

On the other hand, if the dryOff() methods in Soakable and Scrubable have the same signature and return value, then Cup need only implement one dryOff() method:

// In Source Packet in file interface/ex11/Washable.java
interface Washable {
    void wash();
}

// In Source Packet in file interface/ex11/Soakable.java
interface Soakable extends Washable {
    void soak();
    void dryOff();
}

// In Source Packet in file interface/ex11/Scrubable.java
interface Scrubable extends Washable {
    void scrub();
    void dryOff();
}

// In Source Packet in file interface/ex11/Cup.java
class Cup implements Soakable, Scrubable {
    // Implement both Soakable's and Scrubable's
    // dryOff() method with one method body
    public void dryOff() {
        // ...
    }
    public void wash() {
        //...
    }
    public void soak() {
        //...
    }
    public void scrub() {
        //...
    }
    //...
}
If the dryOff() methods declared in Soakable and Scrubable have the same signature but different return types, they couldn't be implemented by the same class.
// In Source Packet in file interface/ex12/Washable.java
interface Washable {
    void wash();
}

// In Source Packet in file interface/ex12/Soakable.java
interface Soakable extends Washable {
    void soak();
    void dryOff();
}

// In Source Packet in file interface/ex12/Scrubable.java
interface Scrubable extends Washable {
    void scrub();
    boolean dryOff();
}

// In Source Packet in file interface/ex12/Cup.java
// THIS WON'T COMPILE, BECAUSE NO CLASS CAN
// IMPLEMENT BOTH Soakable AND Scrubable
class Cup implements Soakable, Scrubable {
    //...
}

If both Soakable and Scrubable declared two constants with the same name, that in itself would never prevent a class from implementing both interfaces. The like-named constants can be of different values and even different types. To refer to one of the constants from with the class, however, you must use the qualified name of the field (the name of the interface, a dot, and the name of the field):

// In Source Packet in file interface/ex13/Washable.java
interface Washable {
    void wash();
}

// In Source Packet in file interface/ex13/Soakable.java
interface Soakable extends Washable {
    int BUBBLE_TOLERANCE = 4;
    void soak();
    void dryOff();
}

// In Source Packet in file interface/ex13/Scrubable.java
interface Scrubable extends Washable {
    double BUBBLE_TOLERANCE = 0.001;
    void scrub();
    void dryOff();
}

// In Source Packet in file interface/ex13/Cup.java
class Cup implements Soakable, Scrubable {
    // Here, can't just say BUBBLE_TOLERANCE. Must
    // use the qualified name.
    public void dryOff() {
        int tol = Soakable.BUBBLE_TOLERANCE;
        double doubleBubble = Scrubable.BUBBLE_TOLERANCE;
        // ...
    }
    public void wash() {
        //...
    }
    public void soak() {
        //...
    }
    public void scrub() {
        //...
    }
    //...
}

Interfaces versus Plain-Old Multiple Inheritance

Interfaces Don't Have the Diamond Problem

Interfaces and Composition

Here talk about the "weight" of plain-old multiple inheritance using the washing machine example. Talk about the advantages of using interfaces to get polymorphism and composition to get reuse.

Interface Naming Conventions

The style conventions proposed by the Java Language Specification encourage you to leave off redundant modifiers in member declarations of interfaces. (Note that this is not an issue with classes, because classes can't have redundant modifiers.)

The Java Language Specification's recommended naming conventions for interfaces are either a noun or noun phrase, if you are using the interface to represent an abstract base class, or an adjective, if you are using the interface to represent a behavior. (Here, both Washable and Breakable are being used to represent a behavior, so their names are adjectives. Using an interface to represent an abstract base class will be discussed later in this chapter.) The capitalization of interface names follows that of classes: the first letter of each word should be upper case, the rest lower case.

Interface Implementation Strategies

An interface can have several possible implementations, each appropriate for different classes of objects or different situations. The implementations can vary in the algorithm or data structures used, yielding, for example, some methods that are faster but use a lot of memory and others that are slower but use memory more conservatively. Sometimes a method declared in an interface simply has a slightly different meaning for the various classes that implement the interface. For example, you might wash an object differently depending upon what the object is. Some ways you could the wash an object are: with soap, water, and a sponge; with sudsy water and a squeegee; with glass cleaner and a paper towel; or with a machine. The appropriate way for a class to implement the wash() method of the Washable interface depends upon that class's unique nature or circumstances.

If you are writing a class that has superinterfaces, you must implement all methods defined in the superinterfaces, or declare the class abstract. There are three approaches you can take to implement those methods:

  1. implement them directly,
  2. inherit an implementation, or
  3. forward the call to another class's implementation.

    If a class has a unique manner of implementing an interface, it can take the first approach and implement it directly. For example, if there is a way to wash cups that is unique to cups, class Cup could declare a wash() method that washes in that unique way. Subclasses of Cup would then have the option of inheriting Cup's implementation (the second approach) or overriding it:

    // In Source Packet in file interface/ex14/Washable.java
    interface Washable {
    	void wash();
    }
    
    // In Source Packet in file interface/ex14/Cup.java
    class Cup implements Washable {
        // Approach one, implement wash() directly
        public void wash() {
            // Sponge off with soap and water.
            // Rinse thoroughly.
        }
        //...
    }
    
    // In Source Packet in file interface/ex14/CoffeeCup.java
    class CoffeeCup extends Cup {
        // Approach two, don't explicitly declare a
        // wash() method. Inherit Cup's implementation
        // of wash().
        //...
    }
    

    Sometimes a single implementation of an interface may make sense for different objects that aren't in the same family of classes. As an example, imagine you wanted to be able to add properties to some of your classes, where a property is a value string indexed by a key string. If a particular property's key string were "color", for example, its value string could be "blue". You want to be able to add properties, remove properties, and lookup a value given a key. Because this behavior is something you'd like to be able to apply to any kind of object, an interface is called for:

    // In Source Packet in file interface/ex15/Propertied.java
    interface Propertied {
    
        void setProperty(String key, String val);
        void removeProperty(String key);
        String getProperty(String key);
    }
    

    You may wish to add properties to both cups and cars, for example, and use the same mechanisms for managing the data. Instead of repeating the same implementation of setProperty(), removeProperty(), and getProperty() in both the Cup and Car classes, you could create a PropertyManager class that implements the Propertied interface:

    // In Source Packet in file interface/ex15/PropertyManager.java
    class PropertyManager implements Propertied {
    
        private java.util.Hashtable props = new java.util.Hashtable();
        public void setProperty(String key, String val) {
            props.put(key, val);
        }
        public void removeProperty(String key) {
            props.remove(key);
        }
        public String getProperty(String key){
            // Returns null if property not found
            return (String) props.get(key);
        }
    }
    

    The PropertyManager class represents one way to implement the Propertied interface. This implementation uses the Hashtable class from the java.util library. (The name java.util.Hashtable is Hashtable's fully qualified name. The details of fully qualified names are described in the next chapter.) Note that this class uses composition. Class PropertyManager has-a Hashtable.

    Cup and Car objects could each contain a PropertyManager object and then forward calls to their PropertyManager. (This is the third approach from the list above.):

    // In Source Packet in file interface/ex15/Cup.java
    class Cup implements Propertied {
    
        private PropertyManager propMgr = new PropertyManager();
    
        public void setProperty(String key, String val) {
            propMgr.setProperty(key, val);
        }
    
        public void removeProperty(String key) {
            propMgr.removeProperty(key);
        }
    
        public String getProperty(String key){
            // Returns null if property not found
            return (String) propMgr.getProperty(key);
        }
        //...
    }
    
    // In Source Packet in file interface/ex15/Car.java
    class Car implements Propertied {
    
        private PropertyManager propMgr = new PropertyManager();
    
        public void setProperty(String key, String val) {
            propMgr.setProperty(key, val);
        }
    
        public void removeProperty(String key) {
            propMgr.removeProperty(key);
        }
    
        public String getProperty(String key){
            // Returns null if property not found
            return (String) propMgr.getProperty(key);
        }
        //...
    }
    

    Any other classes that you later decide could use a Hashtable for implementing the Propertied interface could contain a PropertyManager object and forward calls to it. If you later encounter classes for which it doesn't make sense to use a Hashtable, you could write another class, perhaps LinkedListProperyManager, that implements Propertied in a different way. Those classes for which a Hashtable doesn't make sense could contain a LinkedListProperyManager object and forward calls to it.

    Using Interfaces as Abstract Base Classes

    As mentioned before, the Java Language Specification suggests two conventions for naming interfaces. If the interface represents a behavior, its name should be an adjective. The interfaces given so far as examples in this chapter, Washable, Breakable, and Propertied fall into this category. They represent pure behavior, and their names are adjectives. The other suggested naming convention is for interfaces that serve as abstract base classes. In this case, interfaces should be given names that are nouns or noun phrases, just like classes.

    As described in the previous chapter, you can declare classes abstract. If you have a class that is conceptual only--not one that represents actual objects, but one that represents a category of types--you should declare that class abstract. An abstract class cannot be instantiated. Instead of serving as a blueprint for instantiating objects, an abstract class serves as a base class in a family of types.

    For example, you could decide that in your virtual cafe, you have coffee cups and tea cups. In your inheritance hierarchy, you could define an abstract class Cup that serves as a base class for both CoffeeCup and TeaCup. The abstract Cup class could define abstract methods that both CoffeeCup and TeaCup must implement:

    // In Source Packet in file interface/ex16/Cup.java
    abstract class Cup {
        public abstract void add(int amount);
        public abstract int removeOneSip(int sipSize);
        public abstract int spillEntireContents();
    }
    
    // In Source Packet in file interface/ex16/CoffeeCup.java
    class CoffeeCup extends Cup {
        public void add(int amount) {
            //...
        }
        public int removeOneSip(int sipSize) {
            //...
            return 0;
        }
        public int spillEntireContents() {
            //...
            return 0;
        }
        //...
    }
    
    // In Source Packet in file interface/ex16/TeaCup.java
    class TeaCup extends Cup {
        public void add(int amount) {
            //...
        }
        public int removeOneSip(int sipSize) {
            //...
            return 0;
        }
        public int spillEntireContents() {
            //...
            return 0;
        }
        //...
    }
    

    Given this inheritance hierarchy, shown graphically in Figure 4-8, you could not instantiate a Cup object, but you could use a Cup reference to send messages to a CoffeeCup or TeaCup object. Given a Cup reference that refers to a CoffeeCup or TeaCup, you could invoke add(), releaseOneSip(), or spillEntireContents() on it. The implementation of those methods that actually gets invoked at run-time will depend upon the actual class of the object referred to by the Cup reference.


    Figure 4-8. Cup as an abstract base class

    Since this class Cup contains only public abstract methods, it could alternatively be declared as an interface:

    // In Source Packet in file interface/ex17/Cup.java
    interface Cup {
        void add(int amount);
        int removeOneSip(int sipSize);
        int spillEntireContents();
    }
    
    // In Source Packet in file interface/ex17/CoffeeCup.java
    class CoffeeCup implements Cup {
        public void add(int amount) {
            //...
        }
        public int removeOneSip(int sipSize) {
            //...
            return 0;
        }
        public int spillEntireContents() {
            //...
            return 0;
        }
        //...
    }
    
    // In Source Packet in file interface/ex17/TeaCup.java
    class TeaCup implements Cup {
        public void add(int amount) {
            //...
        }
        public int removeOneSip(int sipSize) {
            //...
            return 0;
        }
        public int spillEntireContents() {
            //...
            return 0;
        }
        //...
    }
    

    Here you are using an interface to represent an abstract base class. As suggested by the Java Language Specification, the name of the class, Cup, is a noun. The inheritance hierarchy for this is shown in Figure 4-9.


    Figure 4-9. Cup as an interface

    In general, if you have an abstract base class that declares only public abstract methods and public static final fields, you may as well make it an interface. Because an abstract class is restricted to single inheritance, but an interface can be multiply inherited, an interface is more flexible than an abstract class. If you want to have any default implementation of methods, or non-public members in the base class, however, it must be an abstract class.


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use