Up to this point, we have used methods in Java essentially like functions in Scheme with the exception of overriding the definition of toString in several classes. What makes object-oriented programming truly powerful is the ability to add new forms of data to our program without modifying any old code. For example, if we later decide to insert links to other directories as additional form of DeptDirectory data, we can simply define a new subclass Link of DeptDirectory with a subDir field referring to the embedded directory (which can be searched using the findAddress method. The new class must define findAddress for the new form of data that it introduces, but none of the existing classes requires any change whatsoever.
In defining a program extension, the added data does not have to be a new subclass of an abstract class like DeptDirectory. The new data can be a subclass of an existing concrete class. For example, we could extend our directory program by defined a class EntryWithPosition extending Entry with a String field title specifying the person's title (graduate student, instructor, professor, chair, etc.) and a getter method getTitle to retrieve it. No revision of the Entry class would be required. Unfortunately, to extract this information using the programming techniques discussed so far, we would have to add a new method findTitle to the composite class hierarchy DeptDirectory--modifying existing code. We will introduce a design pattern, called the visitor pattern near the end of this chapter that eliminates this problem.
When a class C extends another class D, every instance (non-static) member m of D is automatically an instance member of C with one exception: if C redefines the method m1.1 We say that the instance members of class D are inherited by the subclass C. The linguistic convention of automatically incorporating the instance member definitions of a class in each of its subclasses is called inheritance. If an inherited method m is redefined in class C, we say that the new definition overrides the inherited definition.
We have already made extensive use of a limited form of inheritance in the composite pattern. All of the variant classes of in a composite hierarhcy provide definitions for the abstract methods inherited from the abstract superclass. Recall the DeptDirectory and IntList programs. When an abstract method from an abstract class is overridden in a concrete subclass (e.g. findAddress from DeptDirectory in the classes Empty and Cons), the ``missing'' definition inherited from the abstract class is overridden. This special form of overriding is sometimes called method extension because the inherited meaning of the method (nothing) is extended rather than modified.
When a class C overrides a method m in its superclass D, code in the body of C can still invoke the overridden method m from D using the special notation
super.m( ... )The feature can be used to add pre-processing and post-processing code to an inherited method m. The overriding definition of m can check or transform its inputs, invoke super.m on the transformed inputs, and subsequently check or transform the result produced by the super.m call.
It is important to remember that all unqualified member references in inherited code implicitly refer to the implicit method argument this which is bound to an instance of the inheriting class! Hence, the meaning of a method call appearing in inherited code changes if the specified method has been overridden! In practice, this semantic convention gives the behavior that programmers expect for instances of subclasses, but it can occasionally produce surprises--particulary if the method overriding is the result of an accidental rather than an intentional name match.
Finger exercise: Load the superCall sample program into the DrJava Definitions window. The body of method length in class Vector includes calls on the instance method getLeft and getRight. The class TranlatedVector which extends Vector contains a super call on length in its overriding definition of length. To what object is this bound in the inherited length method when it is invoked by the super call?
The meaning of field names in inherited code is even more subtle than the meaning of method names, because fields are never overriden. If the extending class C defines a field with the same name as an inherited field, the inherited field is merely ``shadowed'' exactly as it would be by a local variable with the same name. The inherited field still exists in each instance of C and can be accessed by code in the body of C (assuming that it not private, see Section 1.6.4) by casting the C object to its superclass type D before extracting the field.
If you follow the programming guidelines recommended in this monograph
you can deftly avoid the pathologies discussed in the preceding paragraph.
According to our stylistic rules, field references should never
span class boundaries (with the exception of visitor objects discussed
below in Section ). Hence,
shadowed members are only accessed through getter methods
inherited from the superclass. The only field that can be accessed
directly in the one defined in the current class.