Polymorphism and Interfaces


(来源:http://www.artima.com)

Objects and Java Seminar by Bill Venners
Lecture Handout

Agenda

  • Define polymorphism
  • Talk about upcasting and method invocation
  • Compare static and dynamic binding
  • Talk about abstract classes and interfaces
  • Look at interfaces

Polymorphism Gives You Substitutability

  • In addition to code reuse, inheritance gives you polymorphism.
  • Polymorphism means "many shapes."
  • Gives you substitutability: Code that works with a Cup will work with a CoffeeCup, because a CoffeeCup is-a Cup.


Polymorphism Gives You Extensibility

  • Can add new subclasses, and old code that worked with the base class will still work, even on new subclasses.


Interface and Implementation

  • Polymorphism works because a base class establishes a common interface:

  • Subclasses implement the common interface, each in a way unique to its particular class.

Upcasting

  • Polymorphism enables you to use a variable of a base type to hold a reference to an object of a derived type:

    Liquid myFavoriteBeverage = new Coffee();
    
  • This is called "upcasting" because a Coffee reference is being cast up the inheritance hierarchy to a Liquid reference.

Invoking Methods

  • Which implementation of add() gets invoked?

    Liquid myFavoriteBeverage = new Coffee();
    myFavoriteBeverage.add(new Milk(25));
    
  • The type of the reference, myFavoriteBeverage, is Liquid.
  • The class of the object is Coffee.

Static Binding

  • Static binding is deciding at compile time which method to invoke.
  • With static binding, the method that gets invoked is determined by the type of the reference.
  • In Java, class methods (static methods) are statically bound.

Dynamic Binding

  • Dynamic binding is deciding at run time which method to invoke.
  • With dynamic binding, the method that gets invoked is determined by the class of the object.
  • In Java, instance methods (with 3 exceptions) are dynamically bound.
  • Which implementation of add() gets invoked?

    Liquid myFavoriteBeverage = new Coffee();
    myFavoriteBeverage.add(new Milk(25));
    
  • Three kinds of instance methods get special treatment by the JVM: private methods, <init> methods, and super invocations.

Polymorphism Example

  • To fully realize the wonders of polymorphism, you must send a message to an object without knowing the actual class of the object:

     1 // In file PolymorphInt/examples/ex1/Liquid.java
     2 public class Liquid {
     3 
     4     void swirl(boolean clockwise) {
     5 
     6         // Implement the default swirling
     7         // behavior for liquids
     8         System.out.println(
     9             "Swirling Liquid");
    10     }
    11 }
    
     1 // In file PolymorphInt/examples/ex1/Coffee.java
     2 class Coffee extends Liquid {
     3 
     4     void swirl(boolean clockwise) {
     5 
     6         // Simulate the peculiar swirling
     7         // behavior exhibited by Coffee
     8         System.out.println(
     9             "Swirling Coffee");
    10     }
    11 }
    
    1 // In file PolymorphInt/examples/ex1/Milk.java
    2 class Milk extends Liquid {
    3 
    4     void swirl(boolean clockwise) {
    5 
    6         // Model milk's manner of swirling
    7         System.out.println("Swirling Milk");
    8     }
    9 }
    
     1 // In file PolymorphInt/examples/ex1/Cup.java
     2 class Cup {
     3 
     4       private Liquid innerLiquid;
     5 
     6       Cup(Liquid liq) {
     7             innerLiquid = liq;
     8             // Swirl counterclockwise
     9             innerLiquid.swirl(false);
    10       }
    11 }
    
     1 // In file PolymorphInt/examples/ex1/Example1.java
     2 class Example1 {
     3 
     4     public static void main(String[] args) {
     5 
     6         // First you need various kinds
     7         // of liquid
     8         Liquid liquid = new Liquid();
     9         Liquid coffee = new Coffee();
    10         Liquid milk = new Milk();
    11 
    12         // Now create cups that contain
    13         // various kinds of liquid
    14         Cup cup1 = new Cup(liquid);
    15         Cup cup2 = new Cup(coffee);
    16         Cup cup3 = new Cup(milk);
    17     }
    18 }
    
  • Output of Example2 application will be:

    Swirling Liquid
    Swirling Coffee
    Swirling Milk
    

Inheritance and Polymorphism

  • Subclasses will inherit what they don't implement:

  • Subclasses will always have the base class interface, so can always use subclasses polymorphically as if they were a base class.

Abstract Classes and Methods

  • Can declare classes abstract with the abstract keyword:

    1 // In file PolymorphInt/examples/ex2/Animal.java
    2 public abstract class Animal {
    3 
    4     public void talk() {
    5         System.out.println("Hi there.");
    6     }
    7 }
    8 
    
    1 // In file PolymorphInt/examples/ex2/Dog.java
    2 public class Dog extends Animal {
    3 
    4     public void talk() {
    5         System.out.println("Woof!");
    6     }
    7 }
    8 
    
    1 // In file PolymorphInt/examples/ex2/Cat.java
    2 public class Cat extends Animal {
    3 
    4     public void talk() {
    5         System.out.println("Meow.");
    6     }
    7 }
    8 
    
     1 // In file PolymorphInt/examples/ex2/Example2.java
     2 public abstract class Example2 {
     3 
     4     public static void main(String[] args) {
     5 
     6         Animal dog = new Dog();
     7         Animal cat = new Cat();
     8 
     9         // THIS WOULDN'T COMPILE
    10         // Animal animal = new Animal();
    11 
    12         dog.talk();
    13         cat.talk();
    14     }
    15 }
    16 
    
  • Can also declare methods abstract:

    1 // In file PolymorphInt/examples/ex3/Animal.java
    2 public abstract class Animal {
    3 
    4     public abstract void talk();
    5 }
    6 
    
    1 // In file PolymorphInt/examples/ex3/Dog.java
    2 public class Dog extends Animal {
    3 
    4     public void talk() {
    5         System.out.println("Woof!");
    6     }
    7 }
    8 
    
    1 // In file PolymorphInt/examples/ex3/Cat.java
    2 public class Cat extends Animal {
    3 
    4     public void talk() {
    5         System.out.println("Meow.");
    6     }
    7 }
    8 
    
     1 // In file PolymorphInt/examples/ex3/Example3.java
     2 public abstract class Example3 {
     3 
     4     public static void main(String[] args) {
     5 
     6         Animal dog = new Dog();
     7         Animal cat = new Cat();
     8 
     9         // THIS WOULDN'T COMPILE
    10         // Animal animal = new Animal();
    11 
    12         dog.talk();
    13         cat.talk();
    14     }
    15 }
    16 
    

Interfaces

  • An interface in Java is like an abstract class in which all methods are also abstract:

     1 // In file PolymorphInt/examples/ex4/Washable.java
     2 interface Washable {
     3 
     4     int MAX_SUDS = 10;
     5 
     6     // Returns true if the object needs to be
     7     // washed
     8     //
     9     boolean needsWashing();
    10 
    11     // Washes the object
    12     //
    13     void wash();
    14 }
    15 
    
  • Methods declared in an interface are implicitly public and abstract.
  • Fields declared in an interface are implicitly public, static, and final.
  • The interface represents a special syntax for a special kind of abstract class.
  • Classes and interfaces are types.

Implementing Interfaces

  • Interfaces enable a special kind of multiple inheritance in Java: multiple inheritance of interface, but not of implementation:

     1 // In file PolymorphInt/examples/ex4/Washable.java
     2 interface Washable {
     3 
     4     int MAX_SUDS = 10;
     5 
     6     // Returns true if the object needs to be
     7     // washed
     8     //
     9     boolean needsWashing();
    10 
    11     // Washes the object
    12     //
    13     void wash();
    14 }
    15 
    
     1 // In file PolymorphInt/examples/ex4/Cup.java
     2 public abstract class Cup {
     3 
     4     private int innerLiquid;
     5 
     6     public void add(int amount) {
     7         //...
     8     }
     9 
    10     public int releaseOneSip(int sipSize) {
    11         //...
    12         return 0;
    13     }
    14 
    15     public int spillEntireContents() {
    16         //...
    17         return 0;
    18     }
    19 }
    20 
    
     1 // In file PolymorphInt/examples/ex4/CoffeeCup.java
     2 public class CoffeeCup extends Cup
     3     implements Washable {
     4 
     5     public boolean needsWashing() {
     6         return true;
     7     }
     8 
     9     public void wash() {
    10         //...
    11     }
    12 }
    13 
    

Interfaces Can "Extend" Other Interfaces

  • Interfaces can multiply inherit from other interfaces via the extends keyword:

    1 // On CD-ROM in file interface/ex8/Washable.java
    2 interface Washable {
    3 	void wash();
    4 }
    5 
    
    1 // On CD-ROM in file interface/ex8/Soakable.java
    2 interface Soakable extends Washable {
    3 	void soak();
    4 }
    5 
    
    1 // On CD-ROM in file interface/ex8/Scrubable.java
    2 interface Scrubable extends Washable {
    3 	void scrub();
    4 }
    5 
    
    1 // On CD-ROM in file interface/ex8/BubbleBathable.java
    2 interface BubbleBathable extends Soakable, Scrubable {
    3 	void takeABubbleBath();
    4 }
    5 
    

Classes Can Implement Multiple Interfaces

  • Classes can multiply inherit from multiple interfaces via the implements keyword:

    1 // On CD-ROM in file interface/ex8/Washable.java
    2 interface Washable {
    3 	void wash();
    4 }
    5 
    
    1 // On CD-ROM in file interface/ex8/Soakable.java
    2 interface Soakable extends Washable {
    3 	void soak();
    4 }
    5 
    
    1 // On CD-ROM in file interface/ex8/Scrubable.java
    2 interface Scrubable extends Washable {
    3 	void scrub();
    4 }
    5 
    
    1 // On CD-ROM in file interface/ex8/BubbleBathable.java
    2 interface BubbleBathable extends Soakable, Scrubable {
    3 	void takeABubbleBath();
    4 }
    5 
    
    1 // In file PolymorphInt/examples/ex5/Breakable.java
    2 interface Breakable {
    3     void breakIt();
    4 }
    5 
    
     1 // In file PolymorphInt/examples/ex5/CoffeeCup.java
     2 class CoffeeCup
     3     implements BubbleBathable, Breakable {
     4 
     5     public void wash() {
     6     }
     7 
     8     public void soak() {
     9     }
    10 
    11     public void scrub() {
    12     }
    13 
    14     public void takeABubbleBath() {
    15     }
    16 
    17     public void breakIt() {
    18     }
    19 }
    20 
    
  • If CoffeeCup had neglected to implement all methods declared or inherited by BubbleBathable and Breakable, CoffeeCup would have to be declared abstract.

Exercises

Problem 1.

Take the code from Problem 4 from the Composition and Inheritance exercises and replace class Rodent with an interface. To refresh your memory, here's Problem 4 from Composition and Inheritance:

Create an inheritance hierarchy of Rodent: Mouse, Gerbil, Hamster, etc. In the base class, provide methods that are common to all Rodents, and override these in the derived classes to perform different behaviors depending on the specific type of Rodents. Create an array of Rodent, fill it with different specific types of Rodents, and call your base-class methods to see what happens.

Problem 2.

In a class named Dictionary (in a file named Dictionary.java), create a class (static) variable named WORD_COUNT of type long. Make WORD_COUNT public and final. Initialize WORD_COUNT in a static initializer to the value 1234567. (A static initializer is just an "= <value>" after the declaration of the static variable and before the semicolon. Create a static initialization block inside Dictionary that prints the value of WORD_COUNT out to the standard output with this string message: "Dictionary: WORD_COUNT == <value>".

Create a class named SpellChecker (in a file named SpellChecker.java) that has a main() method of the usual signature (public, static, void, a String array as its only parameter). In the main() method, print the value of Dictionary.WORD_COUNT to the standard output with this message: "SpellChecker: WORD_COUNT == <value>".

Compile these classes and run them to see the output. Take a moment to ponder the deep significance of this output.

Problem 3.

In class SpellChecker from Problem 2, create a new Dictionary object with the new keyword. Place this new statement first in the main method. Store the reference returned from new in a local variable.

Compile these classes again and run them to see the output. Why is this output different from that of Problem 2?

Problem 4.

Since you've been having so much fun with Dictionary and SpellChecker, please return to this code one last time. Take the classes from Problem 3 and edit Dictionary.java. Change the value of WORD_COUNT in Dictionary to 7654321. Recompile just Dictionary.java. It is important that you compile Dictionary.java without recompiling SpellChecker.java. Make sure you type:

javac Dictionary.java

Run the SpellChecker program one last time and observe its startling output. What went wrong?

Problem 5.

In the PolymorphInt/examples/ex1 directory of the sample code, create a class Tea that extends Liquid. Define a swirl() method in Tea that overrides Liquid's implementation. In the body of Tea's swirl() method, just print "Swirling Tea" to the standard output.

Edit Example1.java in the PolymorphInt/examples/ex1 directory. At the end of the main() method, add two more statements. In the first statement, create a new Tea instance and store the reference in a local variable. In the second statement, create a new Cup instance, passing a reference to the Tea object to Cup's constructor. Execute the Example1 application and observe the results.

Problem 6.

In the PolymorphInt/examples/ex5 directory, create a new class Puppy. Make Puppy implement Bubblebathable, but don't actually implement any methods in the body of Puppy (just declare that "Puppy implements Bubblebathable". Attempt to compile your program and observe the error messages generated by the compiler.

Problem 7.

In the Puppy.java class from Problem 6, implement whatever methods you need to implement to get class Puppy to compile. In each method, just print out a message to the standard output saying which method is being called, such as "Puppy: wash() invoked.".

Type in this Problem7 application and run it:

class Problem7 {

    public static void main(String[] args) {

        Puppy puppy = new Puppy();
        soakIt(puppy);
    }

    public static void soakIt(Soakable s) {
        s.wash();
        s.soak();
    }
}

Problem 8.

Copy Problem7.java to Problem8.java, and rename the class it contains Problem8. Changing only the body of the soakIt() method in class Problem8, find a way to invoke, in addition to wash() and soak(), the takeABubbleBath() method on any passed object that happens to implement BubbleBathable. (Do not change the type of the passed s parameter. Your revised soakIt() method should also accept a Soakable reference.) Run the Problem8 program and observe the output.