3.6 Subclasses and InheritanceThe Circle class we've defined is good for abstract mathematical manipulation. For some applications this is exactly what we need. For other applications, we want to be able to manipulate circles and draw them on the screen. This means we need a new class, GraphicCircle, that has all the functionality of Circle, but also has the ability to be drawn. We want to implement GraphicCircle so that it can make use of the code we've already written for Circle. One way to do that is the following:
public class GraphicCircle { // Here is the mathematical circle. public Circle c; // Here are the old methods. public double area() { return c.area(); } public double circumference() { return c.circumference(); } // The new graphic variables and methods go here. public Color outline, fill; public void draw(DrawWindow dw) { /* code omitted */ } } This approach would work, but it is not particularly elegant. The problem is that we have to write stubs for methods like area() and circumference() that have nothing to do with drawing circles. It would be nice if we didn't have to do this. Extending a ClassIn fact, we don't have to do it this way. Java allows us to define GraphicCircle as an extension, or subclass of Circle. Example 3.10 shows how. Note that this example assumes we have two other classes of objects defined: Color, which represents a color, and DrawWindow, a class that has the window into which drawing is done and that defines the primitive methods to do the drawing. Example 3.10: Subclassing a Class
public class GraphicCircle extends Circle { // We automatically inherit the variables and methods of // Circle, so we only have to put the new stuff here. // We've omitted the GraphicCircle constructor, for now. Color outline, fill; public void draw(DrawWindow dw) { dw.drawCircle(x, y, r, outline, fill); } } The extends keyword tells Java that GraphicCircle is a subclass of Circle, and that it inherits the fields and methods of that class. [7] The definition of the draw() method shows variable inheritance--this method uses the Circle variables x, y, and r as if they were defined right in GraphicCircle itself.
GraphicCircle also inherits the methods of Circle. Thus, if we have a GraphicCircle object referred to by variable gc, we can say:
double area = gc.area(); This works just as if the area() method were defined in GraphicCircle itself. Another feature of subclassing is that every GraphicCircle object is also a perfectly legal Circle object. Thus, if gc refers to a GraphicCircle object, we can assign it to a Circle variable, and we can forget all about its extra graphic capabilities: Circle c = gc;. Final ClassesWhen a class is declared with the final modifier, it means that it cannot be extended or subclassed. java.lang.System is an example of a final class. Declaring a class final prevents unwanted extensions to the class. But it also allows the compiler to make some optimizations when invoking the methods of a class. We'll explore this in more detail when we talk about method overriding later in this chapter. Superclasses, Object, and the Class HierarchyIn our example, GraphicCircle is a subclass of Circle. We can also say that Circle is the superclass of GraphicCircle. The superclass of a class is specified in its extends clause:
public class GraphicCircle extends Circle { ... } Every class you define has a superclass. If you do not specify the superclass with an extends clause, the superclass is the class Object. Object is a special class for a couple of reasons:
Because every class has a superclass, classes in Java form a class hierarchy, which can be represented as a tree with Object at its root. Figure 3.1 shows a class hierarchy diagram which includes our Circle and GraphicCircle classes, as well as some of the standard classes from the Java API. The complete class hierarchy for the Java API is diagrammed in the figures of Part V, API Quick Reference. Subclass ConstructorsIn Example 3.10 we left out the constructor method for our new GraphicCircle class. Let's implement that now. Here's one way:
public GraphicCircle(double x, double y, double r, Color outline, Color fill) { this.x = x; this.y = y; this.r = r; this.outline = outline; this.fill = fill; } This constructor relies on the fact that GraphicCircle inherits all the variables of Circle and simply initializes those variables itself. But this duplicates the code of the Circle constructor, and if Circle did more elaborate initialization, it could become quite wasteful. Furthermore, if the Circle class had internal private fields (discussed later), we wouldn't be able to initialize them like this. What we need is a way of calling a Circle constructor from within our GraphicCircle constructor. Example 3.11 shows how we can do this. Example 3.11: Invoking a Superclass's Constructor
public GraphicCircle(double x, double y, double r, Color outline, Color fill) { super(x, y, r); this.outline = outline; this.fill = fill; } super is a reserved word in Java. One of its uses is that shown in the example--to invoke the constructor method of a superclass. Its use is analogous to the use of the this keyword to invoke one constructor method of a class from within another. Using super to invoke a constructor is subject to the same restrictions as using this to invoke a constructor:
Constructor ChainingWhen you define a class, Java guarantees that the class's constructor method is called whenever an instance of that class is created. It also guarantees that the constructor is called when an instance of any subclass is created. In order to guarantee this second point, Java must ensure that every constructor method calls its superclass constructor method. If the first statement in a constructor is not an explicit call to a constructor of the superclass with the super keyword, then Java implicitly inserts the call super()--that is, it calls the superclass constructor with no arguments. If the superclass does not have a constructor that takes no arguments, this causes a compilation error. There is one exception to the rule that Java invokes super() implicitly if you do not do so explicitly. If the first line of a constructor, C1, uses the this() syntax to invoke another constructor, C2, of the class, Java relies on C2 to invoke the superclass constructor, and does not insert a call to super() into C1. Of course, if C2 itself uses this() to invoke a third constructor, C2 does not call super() either, but somewhere along the chain, a constructor either explicitly or implicitly invokes the superclass constructor, which is what is required. Consider what happens when we create a new instance of the GraphicCircle class. First, the GraphicCircle constructor shown in Example 3.11 is invoked. This constructor explicitly invokes a Circle constructor and that Circle constructor implicitly calls super() to invoke the constructor of its superclass, Object. The body of the Object constructor runs first, followed by the body of the Circle constructor, and finally followed by the body of the GraphicCircle constructor. What this all means is that constructor calls are "chained"--any time an object is created, a sequence of constructor methods are invoked, from subclass to superclass on up to Object at the root of the class hierarchy. Because a superclass constructor is always invoked as the first statement of its subclass constructor, the body of the Object constructor always runs first, followed by the body of its subclass, and on down the class hierarchy to the class that is being instantiated. The Default ConstructorThere is one missing piece in the description of constructor chaining above. If a constructor does not invoke a superclass constructor, Java does so implicitly. But what if a class is declared without any constructor at all? In this case, Java implicitly adds a constructor to the class. This default constructor does nothing but invoke the superclass constructor. For example, if we did not declare a constructor for the GraphicCircle class, Java would have implicitly inserted this constructor:
public GraphicCircle() { super(); } Note that if the superclass, Circle(), did not declare a no-argument constructor, then this automatically inserted default constructor would cause a compilation error. If a class does not define a no-argument constructor, then all of its subclasses must define constructors that explicitly invoke the superclass constructor with the necessary arguments. It can be confusing when Java implicitly calls a constructor or inserts a constructor definition into a class--something is happening that does not appear in your code! Therefore, it is good coding style, whenever you rely on an implicit superclass constructor call or on a default constructor, to insert a comment noting this fact. Your comments might look like those in the following example:
class A { int i; public A() { // Implicit call to super(); here. i = 3; } } class B extends A { // Default constructor: public B() { super(); } } If a class does not declare any constructor, it is given a publicly constructor by default. Classes that do not want to be publically instantiated, should declare a protected constructor to prevent the insertion of this public constructor. Classes that never want to be instantiated at all should define a private constructor. Finalizer Chaining?You might assume that since Java chains constructor methods that it also automatically chains the finalizer methods for an object. In other words, you may think that the finalizer method of a class automatically invokes the finalizer of its superclass. In fact, Java does not do this. In practice, finalizer methods are relatively rare, and the need for finalizer chaining rarely arises. If a class B with a finalizer method is a subclass of a class A with its own finalizer method, then B's finalizer should be sure to invoke A's finalizer, explicitly creating a chain of finalizers. This is a little tricky, since finalizers always have the same name (finalize()), and we haven't yet learned how to invoke a method in the superclass when that method is also defined in the subclass. We'll return to the issue of finalizer chaining when we learn how. Shadowed VariablesSuppose that our GraphicCircle class has a new variable that specifies the resolution, in dots per inch, of the DrawWindow object in which it is going to be drawn. And further, suppose that it names that new variable r:
public class GraphicCircle extends Circle { Color outline, fill; float r; // New variable. Resolution in dots-per-inch. public GraphicCircle(double x, double y, double rad, Color o, Color f) { super(x, y, rad); outline = o; fill = f; } public void setResolution(float resolution) { r = resolution; } public void draw(DrawWindow dw) { dw.drawCircle(x, y, r, outline, fill); } } Now, with this resolution variable declared, when we use the variable r in the GraphicCircle class, we are no longer referring to the radius of the circle. The resolution variable r in GraphicCircle shadows the radius variable r in Circle. [8]
So, how can we refer to the radius variable defined in the Circle class when we need it? Recall that using a variable, such as r, in the class in which it is defined is shorthand for:
this.r // Refers to the GraphicCircle resolution variable. As you might guess, you can refer to a variable r defined in the superclass like this:
super.r // Refers to the Circle radius variable. Another way you can do this is to cast this to the appropriate class and then access the variable:
((Circle) this).r This cast is exactly what the super keyword does when used like this. You can use this casting technique when you need to refer to a shadowed variable defined in a class that is not the immediate superclass. For example, if C is a subclass of B, which is a subclass of A, and class C shadows a variable x that is also defined in classes A and B, then you can refer to these different variables from class C as follows:
x // Variable x in class C. this.x // Variable x in class C. super.x // Variable x in class B. ((B)this).x // Variable x in class B. ((A)this).x // Variable x in class A. super.super.x // Illegal; does not refer to x in class A. Note that you cannot refer to a shadowed variable x in the superclass of a superclass with super.super.x. Java does not recognize this syntax. Shadowed Methods?Just as a variable defined in one class can shadow a variable with the same name in a superclass, you might expect that a method in one class could shadow a method with the same name (and same arguments) in a superclass. In a sense, they do: "shadowed" methods are called overridden methods. But method overriding is significantly different than variable shadowing; it is discussed in the sections that follow. |
|