Let's extend our shapes package further. Suppose we now want to implement a number of shapes that not only know their sizes, but also know the position of their center point in the Cartesian coordinate plane. One way to do this is to define an abstract CenteredShape class and then implement various subclasses of it, such as CenteredCircle, CenteredRectangle, and so on.
But we also want these positionable shape classes to support the area() and circumference() methods we've already defined, without reimplementing these methods. So, for example, we'd like to define CenteredCircle as a subclass of Circle, so that it inherits area() and circumference(). But a class in Java can have only one immediate superclass. If CenteredCircle extends Circle, it cannot also extend the abstractCenteredShape class![8]
[8]C++ allows classes to have more than one superclass, using a technique known as multiple inheritance. Multiple inheritance adds a lot of complexity to a language; Java supports what many believe is a more elegant solution.
Java's solution to this problem is called an interface. Although a Java class can extend only a single superclass, it can implement any number of interfaces.
An interface is a reference type that is closely related to a class. Almost everything you've read so far in this book about classes applies equally to interfaces. Defining an interface is a lot like defining an abstract class, except that the keywords abstract and class are replaced with the keyword interface. When you define an interface, you are creating a new reference type, just as you are when you define a class. As its name implies, an interface specifies an interface, or API, for certain functionality. It does not define any implementation of that API, however. There are a number of restrictions that apply to the members of an interface:
An interface contains no implementation whatsoever. All methods of an interface are implicitly abstract, even if the abstract modifier is omitted. Interface methods have no implementation; a semicolon appears in place of the method body. Because interfaces can contain only abstract methods, and class methods cannot be abstract, the methods of an interface must all be instance methods.
An interface defines a public API. All methods of an interface are implicitly public, even if the public modifier is omitted. It is an error to define a protected or private method in an interface.
Although a class defines data and methods that operate on that data, an interface cannot define instance fields. Fields are an implementation detail, and an interface is a pure specification without any implementation. The only fields allowed in an interface definition are constants that are declared both static and final.
An interface cannot be instantiated, so it does not define a constructor.
Example 3-6 shows the definition of an interface named Centered. This interface defines the methods a Shape subclass should implement if it knows the x,y coordinate of its center point.
public interface Centered { public void setCenter(double x, double y); public double getCenterX(); public double getCenterY(); }
Just as a class uses extends to specify its superclass, it can use implements to name one or more interfaces it supports. implements is a Java keyword that can appear in a class declaration following the extends clause. implements should be followed by the name or names of the interface(s) the class implements, with multiple names separated by commas.
When a class declares an interface in its implements clause, it is saying that it provides an implementation (i.e., a body) for each method of that interface. If a class implements an interface but does not provide an implementation for every interface method, it inherits those unimplemented abstract methods from the interface and must itself be declared abstract. If a class implements more than one interface, it must implement every method of each interface it implements (or be declared abstract).
Example 3-7 shows how we can define a CenteredRectangle class that extends our Rectangle class and implements the Centered interface we defined in Example 3-6.
public class CenteredRectangle extends Rectangle implements Centered { // New instance fields private double cx, cy; // A constructor public CenteredRectangle(double cx, double cy, double w, double h) { super(w, h); this.cx = cx; this.cy = cy; } // We inherit all the methods of Rectangle, but must // provide implementations of all the Centered methods. public void setCenter(double x, double y) { cx = x; cy = y; } public double getCenterX() { return cx; } public double getCenterY() { return cy; } }
As I noted earlier, constants can appear in an interface definition. Any class that implements the interface inherits the constants and can use them as if they were defined directly in the class. There is no need to prefix them with the name of the interface or provide any kind of implementation of the constants. When you have a set of constants used by more than one class (e.g., a port number and other protocol constants used by a client and server), it can be convenient to define the necessary constants in an interface that contains no methods. Then, any class that wants to use those constants needs only to declare that it implements the interface. java.io.ObjectStreamConstants is just such an interface.
Suppose we implement CenteredCircle and CenteredSquare just as we implemented CenteredRectangle in Example 3-7. Since each class extends Shape, instances of the classes can be treated as instances of the Shape class, as we saw earlier. Since each class implements Centered, instances can also be treated as instances of that type. The following code demonstrates both techniques:
Shape[] shapes = new Shape[3]; // Create an array to hold shapes // Create some centered shapes, and store them in the Shape[] // No cast necessary: these are all widening conversions shapes[0] = new CenteredCircle(1.0, 1.0, 1.0); shapes[1] = new CenteredSquare(2.5, 2, 3); shapes[2] = new CenteredRectangle(2.3, 4.5, 3, 4); // Compute average area of the shapes and average distance from the origin double totalArea = 0; double totalDistance; for(int i = 0; i < shapes.length; i++) { totalArea += shapes[i].area(); // Compute the area of the shapes if (shapes[i] instanceof Centered) { // The shape is a Centered shape // Note the required cast from Shape to Centered (no cast // would be required to go from CenteredSquare to Centered, however). Centered c = (Centered) shapes[i]; // Assign it to a Centered variable double cx = c.getCenterX(); // Get coordinates of the center double cy = c.getCenterY(); // Compute distance from origin totalDistance += Math.sqrt(cx*cx + cy*cy); } } System.out.println("Average area: " + totalArea/shapes.length); System.out.println("Average distance: " + totalDistance/shapes.length);
This example demonstrates that interfaces are data types in Java, just like classes. When a class implements an interface, instances of that class can be assigned to variables of the interface type. Don't interpret this example, however, to imply that you must assign a CenteredRectangle object to a Centered variable before you can invoke the setCenter() method or to a Shape variable before you can invoke the area() method. CenteredRectangle defines setCenter() and inherits area() from its Rectangle superclass, so you can always invoke these methods.
When defining an abstract type (e.g., Shape) that you expect to have many sub-types (e.g., Circle, Rectangle, Square), you are often faced with a choice between interfaces and abstract classes. Since they have similar features, it is not always clear when to use one over the other.
An interface is useful because any class can implement it, even if that class extends some entirely unrelated superclass. But an interface is a pure API specification and contains no implementation. If an interface has numerous methods, it can become tedious to implement the methods over and over, especially when much of the implementation is duplicated by each implementing class.
On the other hand, a class that extends an abstract class cannot extend any other class, which can cause design difficulties in some situations. However, an abstract class does not need to be entirely abstract; it can contain a partial implementation that subclasses can take advantage of. In some cases, numerous subclasses can rely on default method implementations provided by an abstract class.
Another important difference between interfaces and abstract classes has to do with compatibility. If you define an interface as part of a public API and then later add a new method to the interface, you break any classes that implemented the previous version of the interface. If you use an abstract clas, however, you can safely add nonabstract methods to that class without requiring modifications to existing classes that extend the abstract class.
In some situations, it will be clear that an interface or an abstract class is the right design choice. In other cases, a common design pattern is to use both. First, define the type as a totally abstract interface. Then create an abstract class that implements the interface and provides useful default implementations subclasses can take advantage of. For example:
// Here is a basic interface. It represents a shape that fits inside // of a rectangular bounding box. Any class that wants to serve as a // RectangularShape can implement these methods from scratch. public interface RectangularShape { public void setSize(double width, double height); public void setPosition(double x, double y); public void translate(double dx, double dy); public double area(); public boolean isInside(); } // Here is a partial implementation of that interface. Many // implementations may find this a useful starting point. public abstract class AbstractRectangularShape implements RectangularShape { // The position and size of the shape protected double x, y, w, h; // Default implementations of some of the interface methods public void setSize(double width, double height) { w = width; h = height; } public void setPosition(double x, double y) { this.x = x; this.y = y; } public void translate (double dx, double dy) { x += dx; y += dy; } }
Suppose we want shape objects that can be positioned in terms of not only their center points, but also their upper-left corners. And suppose we also want shapes that can be scaled larger and smaller. Remember that although a class can extend only a single superclass, it can implement any number of interfaces. Assuming we have defined appropriate UpperRightCornered and Scalable interfaces, we can declare a class as follows:
public class SuperDuperSquare extends Shape implements Centered, UpperRightCornered, Scalable { // class members omitted here. }
When a class implements more than one interface, it simply means that it must provide implementations for all abstract methods in all its interfaces.
Interfaces can have subinterfaces, just as classes can have subclasses. A subinterface inherits all the abstract methods and constants of its superinterface and can define new abstract methods and constants. Interfaces are different from classes in one very important way, however: an interface can have an extends clause that lists more than one superinterface. For example, here are some interfaces that extend other interfaces:
public interface Positionable extends Centered { public setUpperRightCorner(double x, double y); public double getUpperRightX(); public double getUpperRightY(); } public interface Transformable extends Scalable, Translatable, Rotatable {} public interface SuperShape implements Positionable, Transformable {}
An interface that extends more than one interface inherits all the abstract methods and constants from each of those interfaces and can define its own additional abstract methods and constants. A class that implements such an interface must implement the abstract methods defined directly by the interface, as well as all the abstract methods inherited from all the superinterfaces.
Sometimes it is useful to define an interface that is entirely empty. A class can implement this interface simply by naming it in its implements clause without having to implement any methods. In this case, any instances of the class become valid instances of the interface. Java code can check whether an object is an instance of the interface using the instanceof operator, so this technique is a useful way to provide additional information about an object. The Cloneable interface in java.lang is an example of this type of marker interface. It defines no methods, but identifies the class as one that allows its internal state to be cloned by the clone() method of the Object class. As of Java 1.1, java.io.Serializable is another such marker interface. Given an arbitrary object, you can determine whether it has a working clone() method with code like this:
Object o; // Initialized elsewhere Object copy; if (o instanceof Cloneable) copy = o.clone(); else copy = null;
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |