3.4. Subclasses and InheritanceThe Circle defined earlier is a simple class that distinguishes circle objects only by their radii. Suppose, instead, that we want to represent circles that have both a size and a position. For example, a circle of radius 1.0 centered at point 0,0 in the Cartesian plane is different from the circle of radius 1.0 centered at point 1,2. To do this, we need a new class, which we'll call PlaneCircle. We'd like to add the ability to represent the position of a circle without losing any of the existing functionality of the Circle class. This is done by defining PlaneCircle as a subclass of Circle, so that PlaneCircle inherits the fields and methods of its superclass, Circle. The ability to add functionality to a class by subclassing, or extending, it is central to the object-oriented programming paradigm. 3.4.1. Extending a ClassExample 3-3 shows how we can implement PlaneCircle as a subclass of the Circle class. Example 3-3. Extending the Circle Classpublic class PlaneCircle extends Circle { // We automatically inherit the fields and methods of Circle, // so we only have to put the new stuff here. // New instance fields that store the center point of the circle public double cx, cy; // A new constructor method to initialize the new fields // It uses a special syntax to invoke the Circle() constructor public PlaneCircle(double r, double x, double y) { super(r); // Invoke the constructor of the superclass, Circle() this.cx = x; // Initialize the instance field cx this.cy = y; // Initialize the instance field cy } // The area() and circumference() methods are inherited from Circle // A new instance method that checks whether a point is inside the circle // Note that it uses the inherited instance field r public boolean isInside(double x, double y) { double dx = x - cx, dy = y - cy; // Distance from center double distance = Math.sqrt(dx*dx + dy*dy); // Pythagorean theorem return (distance < r); // Returns true or false } } Note the use of the keyword extends in the first line of Example 3-3. This keyword tells Java that PlaneCircle extends, or subclasses, Circle, meaning that it inherits the fields and methods of that class.[2] The definition of the isInside() method shows field inheritance; this method uses the field r (defined by the Circle class) as if it were defined right in PlaneCircle itself. PlaneCircle also inherits the methods of Circle. Thus, if we have a PlaneCircle object referenced by variable pc, we can say:
double ratio = pc.circumference() / pc.area(); This works just as if the area() and circumference() methods were defined in PlaneCircle itself. Another feature of subclassing is that every PlaneCircle object is also a perfectly legal Circle object. Thus, if pc refers to a PlaneCircle object, we can assign it to a Circle variable and forget all about its extra positioning capabilities: PlaneCircle pc = new PlaneCircle(1.0, 0.0, 0.0); // Unit circle at the origin Circle c = pc; // Assigned to a Circle variable without casting This assignment of a PlaneCircle object to a Circle variable can be done without a cast. As we discussed in Chapter 2, "Java Syntax from the Ground Up", this is a widening conversion and is always legal. The value held in the Circle variable c is still a valid PlaneCircle object, but the compiler cannot know this for sure, so it doesn't allow us to do the opposite (narrowing) conversion without a cast: // Narrowing conversions require a cast (and a runtime check by the VM) PlaneCircle pc2 = (PlaneCircle) c; boolean origininside = ((PlaneCircle) c).isInside(0.0, 0.0); 3.4.1.1. 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, and it also allows the compiler to make some optimizations when invoking the methods of a class. We'll explore this in more detail later in this chapter, when we talk about method overriding. 3.4.2. Superclasses, Object, and the Class HierarchyIn our example, PlaneCircle is a subclass of Circle. We can also say that Circle is the superclass of PlaneCircle. The superclass of a class is specified in its extends clause: public class PlaneCircle 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 java.lang.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 that includes our Circle and PlaneCircle classes, as well as some of the standard classes from the Java API. Every API quick-reference chapter in Part 2, "API Quick Reference" includes a class-hierarchy diagram for the classes it documents. Figure 3-1. A class hierarchy diagram3.4.3. Subclass ConstructorsLook again at the PlaneCircle() constructor method of Example 3-3: public PlaneCircle(double r, double x, double y) { super(r); // Invoke the constructor of the superclass, Circle() this.cx = x; // Initialize the instance field cx this.cy = y; // Initialize the instance field cy } This constructor explicitly initializes the cx and cy fields newly defined by PlaneCircle, but it relies on the superclass Circle() constructor to initialize the inherited fields of the class. To invoke the superclass constructor, our constructor calls super(). super is a reserved word in Java. One of its uses is to invoke the constructor method of a superclass from within the constructor method of a subclass. This use is analogous to the use of this() to invoke one constructor method of a class from within another constructor method of the same class. Using super() to invoke a constructor is subject to the same restrictions as using this() to invoke a constructor:
The arguments passed to super() must match the parameters of the superclass constructor. If the superclass defines more than one constructor, super() can be used to invoke any one of them, depending on the arguments passed. 3.4.4. Constructor Chaining and the Default ConstructorJava guarantees that the constructor method of a class is called whenever an instance of that class is created. It also guarantees that the constructor is called whenever 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. Thus, if the first statement in a constructor does not explicitly invoke another constructor with this() or super(), 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 implicit invocation causes a compilation error. Consider what happens when we create a new instance of the PlaneCircle class. First, the PlaneCircle constructor is invoked. This constructor explicitly calls super(r) to invoke 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. When it returns, the body of the Circle() constructor runs. Finally, when the call to super(r) returns, the remaining statements of the PlaneCircle() constructor are executed. What all this means is that constructor calls are chained; any time an object is created, a sequence of constructor methods is 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 constructor of its subclass and on down the class hierarchy to the class that is being instantiated. There is an important implication here; when a constructor is invoked, it can count on the fields of its superclass to be initialized. 3.4.4.1. The default constructorThere is one missing piece in the previous description of constructor chaining. If a constructor does not invoke a superclass constructor, Java does so implicitly. But what if a class is declared without a constructor? 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 don't declare a constructor for the PlaneCircle class, Java implicitly inserts this constructor: public PlaneCircle() { super(); }
If the superclass, Circle, doesn't declare a no-argument constructor, the super() call in this automatically inserted default constructor for PlaneCircle() causes a compilation error. In general, if a class does not define a no-argument constructor, all its subclasses must define constructors that explicitly invoke the superclass constructor with the necessary arguments. If a class does not declare any constructors, it is given a no-argument constructor by default. Classes declared public are given public constructors. All other classes are given a default constructor that is declared without any visibility modifier: such a constructor has default visibility. (The notion of visibility is explained later in this chapter.) If you are creating a public class that should not be publicly instantiated, you should declare at least one non-public constructor to prevent the insertion of a default public constructor. Classes that should never be instantiated (such as java.lang.Math or java.lang.System) should define a private constructor. Such a constructor can never be invoked from outside of the class, but it prevents the automatic insertion of the default constructor. 3.4.4.2. Finalizer chaining?You might assume that, since Java chains constructor methods, it also automatically chains the finalizer methods for an object. In other words, you might assume that the finalizer method of a class automatically invokes the finalizer of its superclass, and so on. In fact, Java does not do this. When you write a finalize() method, you must explicitly invoke the superclass finalizer. (You should do this even if you know that the superclass does not have a finalizer because a future implementation of the superclass might add a finalizer.) As we saw in our example finalizer earlier in the chapter, you can invoke a superclass method with a special syntax that uses the super keyword: // Invoke the finalizer of our superclass. super.finalize(); We'll discuss this syntax in more detail when we consider method overriding. In practice, the need for finalizer methods, and thus finalizer chaining, rarely arises. 3.4.5. Shadowing Superclass FieldsFor the sake of example, imagine that our PlaneCircle class needs to know the distance between the center of the circle and the origin (0,0). We can add another instance field to hold this value: public double r; Adding the following line to the constructor computes the value of the field: this.r = Math.sqrt(cx*cx + cy*cy); // Pythagorean Theorem But wait, this new field r has the same name as the radius field r in the Circle superclass. When this happens, we say that the field r of PlaneCircleshadows the field r of Circle. (This is a contrived example, of course: the new field should really be called distanceFromOrigin. Although you should attempt to avoid it, subclass fields do sometimes shadow fields of their superclass.) With this new definition of PlaneCircle, the expressions r and this.r both refer to the field of PlaneCircle. How, then, can we refer to the field r of Circle that holds the radius of the circle? There is a special syntax for this that uses the super keyword: r // Refers to the PlaneCircle field this.r // Refers to the PlaneCircle field super.r // Refers to the Circle field Another way to refer to a shadowed field is to cast this (or any instance of the class) to the appropriate superclass and then access the field: ((Circle) this).r // Refers to field r of the Circle class This casting technique is particularly useful when you need to refer to a shadowed field defined in a class that is not the immediate superclass. Suppose, for example, that classes A, B, and C all define a field named x and that C is a subclass of B, which is a subclass of A. Then, in the methods of class C, you can refer to these different fields as follows: x // Field x in class C this.x // Field x in class C super.x // Field x in class B ((B)this).x // Field x in class B ((A)this).x // Field x in class A super.super.x // Illegal; does not refer to x in class A You cannot refer to a shadowed field x in the superclass of a superclass with super.super.x. This is not legal syntax. Similarly, if you have an instance c of class C, you can refer to the three fields named x like this: c.x // Field x of class C ((B)c).x // Field x of class B ((A)c).x // Field x of class A So far, we've been discussing instance fields. Class fields can also be shadowed. You can use the same super syntax to refer to the shadowed value of the field, but this is never necessary since you can always refer to a class field by prepending the name of the desired class. Suppose that the implementer of PlaneCircle decides that the Circle.PI field does not express π to enough decimal places. She can define her own class field PI: public static final double PI = 3.14159265358979323846; Now, code in PlaneCircle can use this more accurate value with the expressions PI or PlaneCircle.PI. It can also refer to the old, less accurate value with the expressions super.PI and Circle.PI. Note, however, that the area() and circumference() methods inherited by PlaneCircle are defined in the Circle class, so they use the value Circle.PI, even though that value is shadowed now by PlaneCircle.PI. 3.4.6. Overriding Superclass MethodsWhen a class defines an instance method using the same name, return type, and parameters as a method in its superclass, that method overrides the method of the superclass. When the method is invoked for an object of the class, it is the new definition of the method that is called, not the superclass's old definition. Method overriding is an important and useful technique in object-oriented programming. PlaneCircle does not override either of the methods defined by Circle, but suppose we define another subclass of Circle, named Ellipse.[3] In this case, it is important for Ellipse to override the area() and circumference() methods of Circle, since the formulas used to compute the area and circumference of a circle do not work for ellipses.
The upcoming discussion of method overriding considers only instance methods. Class methods behave quite differently, and there isn't much to say. Like fields, class methods can be shadowed by a subclass, but not overridden. As I noted earlier in this chapter, it is good programming style to always prefix a class method invocation with the name of the class in which it is defined. If you consider the class name part of the class method name, the two methods have different names, so nothing is actually shadowed at all. It is, however, illegal for a class method to shadow an instance method. Before we go any further with the discussion of method overriding, you need to be sure you understand the difference between method overriding and method overloading. As we discussed in Chapter 2, "Java Syntax from the Ground Up", method overloading refers to the practice of defining multiple methods (in the same class) that have the same name, but different parameter lists. This is very different from method overriding, so don't get them confused. 3.4.6.1. Overriding is not shadowingAlthough Java treats the fields and methods of a class analogously in many ways, method overriding is not like field shadowing at all. You can refer to shadowed fields simply by casting an object to an instance of the appropriate superclass, but you cannot invoke overridden instance methods with this technique. The following code illustrates this crucial difference: class A { // Define a class named A int i = 1; // An instance field int f() { return i; } // An instance method static char g() { return 'A'; } // A class method } class B extends A { // Define a subclass of A int i = 2; // Shadows field i in class A int f() { return -i; } // Overrides instance method f in class A static char g() { return 'B'; } // Shadows class method g() in class A } public class OverrideTest { public static void main(String args[]) { B b = new B(); // Creates a new object of type B System.out.println(b.i); // Refers to B.i; prints 2 System.out.println(b.f()); // Refers to B.f(); prints -2 System.out.println(b.g()); // Refers to B.g(); prints B System.out.println(B.g()); // This is a better way to invoke B.g() A a = (A) b; // Casts b to an instance of class A System.out.println(a.i); // Now refers to A.i; prints 1 System.out.println(a.f()); // Still refers to B.f(); prints -2 System.out.println(a.g()); // Refers to A.g(); prints A System.out.println(A.g()); // This is a better way to invoke A.g() } } While this difference between method overriding and field shadowing may seem surprising at first, a little thought makes the purpose clear. Suppose we have a bunch of Circle and Ellipse objects we are manipulating. To keep track of the circles and ellipses, we store them in an array of type Circle[]. (We can do this because Ellipse is a subclass of Circle, so all Ellipse objects are legal Circle objects.) When we loop through the elements of this array, we don't have to know or care whether the element is actually a Circle or an Ellipse. What we do care about very much, however, is that the correct value is computed when we invoke the area() method of any element of the array. In other words, we don't want to use the formula for the area of a circle when the object is actually an ellipse! Seen in this context, it is not surprising at all that method overriding is handled differently by Java than field shadowing. 3.4.6.2. Dynamic method lookupIf we have a Circle[] array that holds Circle and Ellipse objects, how does the compiler know whether to call the area() method of the Circle class or the Ellipse class for any given item in the array? In fact, the compiler does not know this because it cannot know it. The compiler knows that it does not know, however, and produces code that uses dynamic method lookup at runtime. When the interpreter runs the code, it looks up the appropriate area() method to call for each of the objects in the array. That is, when the interpreter interprets the expression o.area(), it checks the actual type of the object referred to by the variable o and then finds the area() method that is appropriate for that type. It does not simply use the area() method that is statically associated with the type of the variable o. This process of dynamic method lookup is sometimes also called virtual method invocation.[4]
3.4.6.3. Final methods and static method lookupVirtual method invocation is fast, but method invocation is faster when no dynamic lookup is necessary at runtime. Fortunately, there are a number of situations in which Java does not need to use dynamic method lookup. In particular, if a method is declared with the final modifier, it means that the method definition is the final one; it cannot be overridden by any subclasses. If a method cannot be overridden, the compiler knows that there is only one version of the method, and dynamic method lookup is not necessary.[5] In addition, all methods of a final class are themselves implicitly final and cannot be overridden. As we'll discuss later in this chapter, private methods are not inherited by subclasses and, therefore, cannot be overridden (i.e., all private methods are implicitly final). Finally, class methods behave like fields (i.e., they can be shadowed by subclasses but not overridden). Taken together, this means that all methods of a class that is declared final, as well as all methods that are final, private, or static, are invoked without dynamic method lookup. These methods are also candidates for inlining at runtime by a just-in-time compiler ( JIT) or similar optimization tool.
3.4.6.4. Invoking an overridden methodWe've seen the important differences between method overriding and field shadowing. Nevertheless, the Java syntax for invoking an overridden method is quite similar to the syntax for accessing a shadowed field: both use the super keyword. The following code illustrates: class A { int i = 1; // An instance field shadowed by subclass B int f() { return i; } // An instance method overridden by subclass B } class B extends A { int i; // This field shadows i in A int f() { // This method overrides f() in A i = super.i + 1; // It can retrieve A.i like this return super.f() + i; // It can invoke A.f() like this } } Recall that when you use super to refer to a shadowed field, it is the same as casting this to the superclass type and accessing the field through that. Using super to invoke an overridden method, however, is not the same as casting this. In other words, in the previous code, the expression super.f() is not the same as ((A)this).f(). When the interpreter invokes an instance method with this super syntax, a modified form of dynamic method lookup is performed. The first step, as in regular dynamic method lookup, is to determine the actual class of the object through which the method is invoked. Normally, the dynamic search for an appropriate method definition would begin with this class. When a method is invoked with the super syntax, however, the search begins at the superclass of the class. If the superclass implements the method directly, that version of the method is invoked. If the superclass inherits the method, the inherited version of the method is invoked. Note that the super keyword invokes the most immediately overridden version of a method. Suppose class A has a subclass B that has a subclass C, and all three classes define the same method f(). Then the method C.f() can invoke the method B.f(), which it overrides directly, with super.f(). But there is no way for C.f() to invoke A.f() directly: super.super.f() is not legal Java syntax. Of course, if C.f() invokes B.f(), it is reasonable to suppose that B.f() might also invoke A.f(). This kind of chaining is relatively common when working with overridden methods: it is a way of augmenting the behavior of a method without replacing the method entirely. We saw this technique in the the example finalize() method shown earlier in the chapter: that method invoked super.finalize() to run its superclass finalization method. Don't confuse the use of super to invoke an overridden method with the super() method call used in constructor methods to invoke a superclass constructor. Although they both use the same keyword, these are two entirely different syntaxes. In particular, you can use super to invoke an overridden method anywhere in the overriding method, while you can use super() only to invoke a superclass constructor as the very first statement of a constructor. It is also important to remember that super can be used only to invoke an overridden method from within the method that overrides it. Given an Ellipse object e, there is no way for a program that uses an object (with or without the super syntax) to invoke the area() method defined by the Circle class on this object. I've already explained that class methods can shadow class methods in superclasses, but cannot override them. The preferred way to invoke class methods is to include the name of the class in the invocation. If you do not do this, however, you can use the super syntax to invoke a shadowed class method, just as you would invoke an instance method or refer to a shadowed field. Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|