Section 4.3
Abstract Classes and Interfaces


IN THE PREVIOUS SECTION, I discussed a Shape class with subclasses Rectangle, Oval, and RoundRect representing particular types of shapes. Each of these classes includes a redraw() method. The redraw() method in the Rectangle class draws a rectangle. The redraw() method in the Oval class draws an oval. The redraw() method in the RoundRect class draws a rounded rectangle. What does the redraw() method in the Shape class do? How should it be defined?

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 like rectangles and ovals can be drawn. So, why should there even be a redraw() method in the Shape class? Well, it has to be there, or it would be illegal to call it in the setColor() method of the Shape class, and it would be illegal to write "oneShape.redraw();", where oneShape is a variable of type Shape.

Nevertheless the version of redraw() in the Shape class will never be called. 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 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. For an abstract method, the block of code that gives the implementation of an ordinary method is replaced by a semicolon. An implementation must be provided for the abstract method in any concrete subclass of the abstract class. Here's what the Shape class would look like as an abstract class:

       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, but it is the only class that has this property.) 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, belonging to any class. Object is the most abstract class of all!

The Object class actually finds a use in some cases where objects of a very general sort are being manipulated. For example, java has a standard class, java.util.Vector, that represents a list of Objects. (The Vector class is in the package java.util. If you want to use this class in a program you write, you would ordinarily use an import statement to make it possible to use the short name, Vector, instead of the full name, java.util.Vector. See Section 2.6 for a discussion of packages and import.) The Vector class is very convenient, because a Vector can hold any number of objects, and it will grow, when necessary, as objects are added to it. Since the items in the list are of type Object, the list can actually hold objects of any type.

A program that wants to keep track of various Shapes that have been drawn on the screen can store those shapes in a Vector. Suppose that the Vector is named listOfShapes. A shape, oneShape, can be added to the end of the list by calling the instance method "listOfShapes.addElement(oneShape);". The shape could be removed from the list with "listOfShapes.removeElement(oneShape);". The number of shapes in the list is given by the function "listOfShapes.size()".) And it is possible to retrieve the i-th object from the list by calling the function "listOfShapes.elementAt(i)". (Items in the list are numbered from 0 to listOfShapes.size() - 1. However, note that this method returns an Object, not a Shape. (Of course, the people who wrote the Vector class didn't even know about Shapes!) Since you know that the items in the list are, in fact, Shapes and not just Objects, you can type cast the Object returned by listOfShapes.elementAt(i) to be a value of type Shape:

Shape s = (Shape)listOfShapes.elementAt(i);

Let's say, for example, that you want to redraw all the shapes in the list. You could do this with a simple for loop, which is lovely example of object-oriented programming and polymorphism:

         for (int i = 0; i < listOfShapes.size(); i++) {
            Shape s = (Shape)listOfShapes.elementAt(i);
            s.redraw();
         }

It might be worthwhile to look at an applet that actually uses an abstract Shape class and a Vector to hold a list of shapes:

Sorry, your browser doesn't
support Java.

If you click one of the buttons along the bottom of this applet, a shape will be added to the screen in the upper left corner of the applet. The color of the shape is given by the "Choice menu" in the lower right. Once a shape is on the screen, you can drag it around with the mouse. A shape will maintain the same front-to-back order with respect to other shapes on the screen, even while you are dragging it. However, you can move a shape out in front of all the other shapes if you hold down the shift key as you click on it.

In this applet the only time when the actual class of a shape is used is when that shape is added to the screen. Once the shape has been created, it is manipulated entirely as an abstract shape. The routine that implements dragging, for example, works only with variables of type Shape. As the Shape is being dragged, the dragging routine calls the Shape's draw method over and over. The Shape is responsible for drawing itself correctly. If I wanted to add a new type of shape to the program, I would define a new subclass of Shape, add another button to the applet, and program the button to add the correct type of shape to the screen. No other changes in the programming would be necessary.

You might want to look at the source code for this applet, ShapeDraw.java, even though you won't be able to understand all of it at this time. Even the definitions of the shape classes are somewhat different from those I described in this chapter. (For example, the draw() method used in the applet has a parameter of type Graphics. This parameter is required because of the way Java handles all drawing.) I'll return to this example in later chapters when you know more about applets. However, it would still be worthwhile to look at the definition of the Shape class and its subclasses in the source code for the applet. You can also check how a Vector is used to hold a list of the shapes on the screen.


Interfaces

Some object-oriented programming languages, such as C++, allow a class to extend two or more superclasses. This is called multiple inheritance. In the illustration below, for example, class E is shown as having both class A and class B as direct superclasses, while class F has three direct superclasses.

Such multiple inheritance is not allowed in Java. The designers of Java wanted to keep the language reasonably simple, and felt that the benefits of multiple inheritance were not worth the cost in increased complexity. However, Java does have a feature that can be used to accomplish many of the same goals as multiple inheritance: interfaces.

We've encountered the term "interface" before, in connection with black boxes in general and subroutines in particular. The interface of a subroutine consists of the name of the subroutine, its return type, and the number and types of its parameters. This is the information you need to know if you want to call the subroutine. A subroutine also has an implementation: the block of code which defines it and which is executed when the subroutine is called.

In Java, interface is a reserved word with an additional meaning. An "interface" in Java consists of a set of subroutine interfaces, without any associated implementations. A class can implement an interface by providing an implementation for each of the subroutines specified by the interface. (An interface can also include definitions of constants -- that is, variables declared as final static. The constants are there as a convenience to provide meaningful names for certain quantities.) Here is an example of a very simple Java interface:

       public interface Drawable {
          public void draw();
       }

This looks much like a class definition, except that the implementation of the method draw() is omitted. A class that implements the interface Drawable must provide an implementation for this method. Of course, it can also include other methods and variables. For example,

        class Line implements Drawable {
            public void draw() {
                . . . // do something -- presumably, draw a line
            }
            . . . // other methods and variables
         }

While a class can extend only one other class, it can implement any number of interfaces. In fact, a class can both extend another class and implement one or more interfaces. So, we can have things like

        class FilledCircle extends Circle 
                                implements Drawable, Fillable {
           . . .
        }

The point of all this is that, although interfaces are not classes, they are something very similar. An interface is very much like an abstract class, that is a class that can never be used for constructing objects, but can be used as a basis for building other classes. The subroutines in an interface are abstract methods, which must be implemented in any concrete class that implements the interface. And as with abstract classes, even though you can't construct an object from an interface, you can declare a variable whose type is given by the interface. For example, if Drawable is an interface, and if Line and FilledCircle are classes that implement Drawable, then you could say:

       Drawable figure = new Line();  // variable of type Drawable, referring
                                      //   to an object of class Line
       . . .    // do some stuff with figure
       figure.draw();   // calls draw() method from class Line
       figure = new FilledCircle();   // now, figure refers to an object
                                      //   of class FilledCircle
       figure.draw();   // calls draw() method from class FilledCircle

A variable of type Drawable can refer to any object of any class that implements the Drawable interface. A statement like figure.draw(), above, is legal because any such class has a draw() method.

Note that a type is something that can be used to declare variables. A type can also be used to specify the type of a parameter in a subroutine, or the return type of a function. In Java, a type can be either a class, an interface, or one of the eight built-in primitive types. These are the only possibilities. Of these, however, only classes can be used to construct new objects.

You are not likely to need to write your own interfaces until you get to the point of writing fairly complex programs. However, there are a few interfaces that are used in important ways in Java's standard packages. You'll learn about some of these standard interfaces in the next few chapters.


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