Section 4.3
Inheritance and Polymorphism


A CLASS REPRESENTS A SET OF OBJECTS which share the same structure and behaviors. The class determines the structure of objects by specifying variables that are contained in each instance of the class, and it determines behavior of objects by providing the methods that express the behavior of those instances. This is a powerful idea. However, something like this can be done in most programming languages. The central new idea in object-oriented programming -- the idea that really distinguishes it from traditional programming -- is to allow classes to express the similarities among objects that share some, but not all, of their structure and behavior. Such similarities can expressed using inheritance.

The term "inheritance" refers to the fact that one class can inherit part or all of its structure and behavior from another class. The class that does the inheriting is said to be a subclass of the class from which it inherits. If class B is a subclass of class A, we also say that class A is a superclass of class B. (Sometimes the terms derived class and base class are used instead of subclass and superclass.) A subclass can add to the structure and behavior that it inherits. It can also replace or modify inherited behavior (though not inherited structure). The relationship between subclass and superclass is sometimes shown by a diagram in which the subclass is shown below, and connected to, its superclass.

In Java, when you create a new class, you can declare that it is a subclass of an existing class. If you are definining a class named "B" and you want it to be a subclass of a class named "A", you would write

        class B extends A {
            .
            .  // additions to, and modifications of,
            .  // stuff inherited from class A
            .
        }

Several classes can be declared as subclasses of the same superclass. The subclasses, which could be considered to be "sibling classes," share some sturctures and behaviors -- namely, the ones they inherit from their common superclass. The superclass can be seen as expressing those shared structures and behaviors. In the diagram to the left, classes B, C, and D are sibling classes. Inheritance can also extend over several "generations" of classes. This is shown in the diagram, where class E is a subclass of class D which is itself a subclass of class A. In this case, class E is considered to be a subclass of class A, even though the inheritance is indirect.



Let's look at an example. Suppose that a program has to deal with motor vehicles, including cars, trucks, and motorcycles. (This might be a program used by a Department of Motor Vehicles to keep track of registrations.) The program could use a class named Vehicle to represent all types of vehicles. The Vehicle class might include instance variables such as registration number and owner and instance methods such as transferOwnership(). These are variables and methods common to all vehicles. Three subclasses of Vehicle -- Car, Truck, and Motorcycle -- could then be used to hold variables and methods specific to particular types of vehicles. The Car class might add an instance variable numberOfDoors, the Truck class might have numberOfAxels, and the Motorcycle could have a boolean variable hasSidecar. (Well, it could in theory at least.) The declarations of these classes in Java program would look, in outline, like this:

     class Vehicle {
        int registrationNumber;
        Person owner;  // (assuming that a Person class has been defined)
        void transferOwnership(Person newOwner) {
            . . .
        }
        . . .
     }
     class Car extends Vehicle {
        int numberOfDoors;
        . . .
     }
     class Truck extends Vehicle {
        int numberOfAxels;
        . . .
     }
     class Motorcycle extends Vehicle {
        boolean hasSidecar;
        . . .
     }

Suppose that myCar is a variable of type Car that has been declared and initialized with the statement

Car myCar = new Car();

Then it would be legal to refer to myCar.numberOfDoors, since numberOfDoors is an instance variable in the class Car. But since class Car extends class Vehicle, myCar.registrationNumber, myCar.owner, and myCar.transferOwnership() also exist.

Now, in the real world, cars, trucks, and motorcycles are in fact vehicles. The same is true in a program. That is, an object of type Car or Truck or Motorcycle is automatically an object of type Vehicle. The practical effect of this is that an object of type Car can be assigned to a variable of type Vehicle. That is, it would be legal to say

Vehicle myVehicle = myCar;

or even

Vehicle myVehicle = new Car();

After either of these statements, the variable myVehicle holds a reference to an object that happens to be an instance of the class Car. The object "remembers" that it is in fact a Car, and not just a Vehicle. Information about the actual class of an object is stored as part of that object. On the other hand, if myVehicle is a variable of type Vehicle the statement

Car myCar = myVehicle;

would be illegal because myVehicle could potentially refer to other types of vehicles besides cars. This is similar to a problem we saw previously in Section 2.6: The computer will not allow you to assign an int value to a variable of type short, because not every int is a short. Similarly, it will not allow you to assign a value of type Vehicle to a variable of type Car because not every vehicle is a car. As in the cast of ints and shorts, the solution here is to use type casting. If, for some reason, you happen to know that myVehicle does in fact refer to a Car, you can use the type cast (Car)myVehicle to tell the computer to treat myVehicle as if it were actually of type Car. So, you could say

Car myCar = (Car)myVehicle;

and you could even refer to ((Car)myVehicle).numberOfDoors. Note that for object types, when the computer executes a program, it checks whether type casts are valid. So, for example, if myVehicle refers to an object of type Truck, then the type cast (Car)myVehicle will produce an error.


As another example, consider a program that deals with shapes drawn on the screen. Let's say that the shapes include rectangles, ovals, and roundrects of various colors.

(illustration showing various shapes)

Three classes, Rectangle, Oval, and RoundRect, could be used to represent the three types of shapes. These three classes could have a common superclass, Shape, to represent features that all three shapes have in common. The Shape class could include instance variables to represent the color, position, and size of a shape. It could include instance methods for changing the color, position, and size of a shape. Changing the color, for example, would involve changing the value of an instance variable, and then redrawing the shape in its new color:

       class Shape {
       
           Color color;   // color of shape.  Note that class Color
                          // is defined in package java.awt.  Assume
                          // that this class has been imported.
                          
           void setColor(Color newColor) {
                 // method to change the color of the shape
              color = newColor; // change value of instance variable
              redraw(); // redraw shape, which will appear in new color
           }
           
           void redraw() {
                 // method for redrawing the shape
              ? ? ?  // what commands should go here?
           }

           . . .          // more instance variables and methods

       } // end of class Shape

Now, you might see a problem here with the method redraw(). The problem is that each different type of shape is drawn differently. The method setColor() can be called for any type of shape. How does the computer know which shape to draw when it executes the redraw()? Informally, we can answer the question like this: The computer executes redraw() by asking the shape to redraw itself. Every shape object knows what it has to do to redraw itself.

In practice, this means that each of the specific shape classes has its own redraw() method:

       class Rectangle extends Shape {
          void redraw() {
             . . .  // commands for redrawing a rectangle
          }
          . . . // possibly, more methods and variables
       }
       class Oval extends Shape {
          void redraw() {
             . . .  // commands for redrawing an oval
          }
          . . . // possibly, more methods and variables
       }
       class RoundRect extends Shape {
          void redraw() {
             . . .  // commands for redrawing a rounded rectangle
          }
          . . . // possibly, more methods and variables
       }

If oneShape is a variable of type Shape, it could refer to an object of any of the types Rectangle, Oval, or RoundRect. As a program executes, and the value of oneShape changes, it could even refer to objects of different types at different times! Whenever the statement

oneShape.redraw();

is executed, the redraw method that is actually called is the one appropriate for the type of object to which oneShape actually refers. There may be no way of telling, from looking at the text of the program, what shape this statement will draw, since it depends on the value that oneShape happens to have when the program is executed. Even more is true. Suppose the statement is in a loop and gets executed many times. If the value of oneShape changes as the loop is executed, it is possible that the very same statement "oneShape.redraw();" will call different methods and draw different shapes as it is executed over and over. We say that the redraw() method is polymorphic. Polymorphism is one of the major distinguishing features of object-oriented programming.

Perhaps this becomes more understandable if we change our terminology a bit: In object-oriented programming, calling a method is often referred to as sending a message to an object. The object responds to the message by executing the appropriate method. The statement "oneShape.redraw();" is a message to the object referred to by oneShape. Since that object knows what type of object it is, it knows how it should respond to the message. From this point of view, the computer always executes "oneShape.redraw();" in the same way: by sending a message. The response to the message depends, naturally, on who receives it. From this point of view, objects are active entities that send and receive messages, and polymorphism is a natural, even necessary, part of this view. Polymorphism just means that different objects can respond to the same message in different ways.

One of the most beautiful things about polymorphism is that it let's code that you write do things that you didn't even conceive of, at the time you wrote it. If for some reason, I decide that I want to add beveled rectangles to the types of shapes my program can deal with, I can write a new subclass, BeveledRect, of class Shape and give it is own redraw() method. Automatically, code that I wrote previously -- such as the statement oneShape.redraw() and the call to redraw() inside Shape's setColor() method -- can now suddenly start drawing beveled rectangles!



Look back at the Shape class. It includes a redraw() method; it has to include this method, or else it would be illegal to call redraw() from inside Shape's setColor() method. But how should we define it? The answer may be surprising: We should leave it blank! The fact is that the class Shape represents the abstract idea of a shape, and there is no way to draw such a thing. Only particular, concrete shapes can be drawn. In fact, if you think about it, there can never be any reason to construct an actual object of type Shape. You can have variables of type Shape, but the objects they refer to will always belong to one of the subclasses of Shape. We say that Shape is an abstract class. An abstract class is one that is not used to construct objects, but only as a basis for making subclasses. An abstract class exists only to express the common properties of all its subclasses.

Similarly, we could say that the redraw() method in class Shape is an abstract method, since it is never meant to be called. In fact, there is nothing for it to do -- any actual redrawing is done by redraw() methods in one of the subclasses of Shape. The redraw() method in Shape has to be there. But it is there only to tell the computer that all Shapes understand the redraw message. As an abstract method, it exists merely to specify the common interface of all the actual, concrete versions of redraw() in the subclasses of Shape. There is no reason for the abstract redraw() in class Shape to contain any code at all.

Shape and its redraw() method are semantically abstract. You can also tell the computer, syntactically, that they are abstract by adding the modifier "abstract" to their definitions:

       abstract class Shape {
       
           Color color;   // color of shape. 
                                     
           void setColor(Color newColor) {
                 // method to change the color of the shape
              color = newColor; // change value of instance variable
              redraw(); // redraw shape, which will appear in new color
           }
           
           abstract void redraw();
                 // abstract method -- must be redefined in 
                 // concrete subclasses

           . . .          // more instance variables and methods

       } // end of class Shape

Once you have done this, it becomes illegal to try to create actual objects of type Shape, and the computer will report an error if you try to do so.


In Java, every class that you declare has a superclass. If you don't specify a superclass, then the superclass is automatically taken to be Object, a predefined class that is part of the package java.lang. (The class Object itself has no superclass.) Thus,

class myClass { . . .

is exactly equivalent to

class myClass extends Object { . . .

Every other class is, directly or indirectly, a subclass of Object. This means that any object, belonging to any class whatsoever, can be assigned to a variable of type Object. The class Object represents very general properties that are shared by all objects, of any class. Object is the most abstract class of all!


[ Next Section | Previous Section | Chapter Index | Main Index ]