Managing Inheritance

The Object class, defined in the java.lang package, defines and implements behavior that every class needs. As depicted in Figure 66, many classes derive from Object, many classes derive from those classes, and so on, forming a hierarchy of classes.

Figure 66. All classes are descendants of the Object class.

At the top of the hierarchy, Object is the most general of all classes. Classes near the bottom of the hierarchy provide for more specialized behavior. A subclass derives from another class. The term superclass refers to a class's direct ancestor or to any of its ascendant classes. Every class has one and only one immediate superclass.

A subclass inherits all the member variables and methods from its superclass. However, the subclass might not have access to an inherited member variable or method. For example, a subclass cannot access a private member inherited from its superclass. One might say, then, that the item was not inherited at all. But the item is inherited. This becomes important when using an inner class, which does have access to its enclosing class's private members. Note that constructors are not members and so are not inherited by subclasses.

Overriding and Hiding Methods

An instance method in a subclass with the same signature and return type as an instance method in the superclass overrides the superclass's method. (Remember that a method's signature is its name and the number and the type of its arguments.) The ability of a subclass to override a method allows a class to inherit from a superclass whose behavior is "close enough" and then to modify behavior as needed. For example, the Object class contains an instance method called toString that returns a string representation of an instance of that type. Every class inherits this method. The implementation in Object is not very useful for subclasses; thus, we recommend overriding this method to provide better information for your classes. It's particularly useful for debugging. Here's an example of overriding toString:

public class MyClass { private int anInt = 4; //Overrides toString in Object class. public String toString() { return "Instance of MyClass. anInt = " + anInt; } }

The overriding method has the same name, number and type of arguments, and return value as the method it overrides. The overriding method can have a different throws clause as long as it doesn't specify any types not specified by the throws clause in the overridden method. Also, the access specifier for the overriding method can allow more but not less access than the overridden method. For example, a protected method in the superclass can be made public but not private.

A subclass cannot override methods that are declared final in the superclass (by definition, final methods cannot be overridden). If you attempt to override a final method, the compiler displays an error message. The section Writing Final Classes and Methods (page 213) discusses final methods in detail.

A subclass must override methods that are declared abstract in the superclass, or the subclass itself must be abstract. The section Writing Abstract Classes and Methods (page 214) discusses abstract classes and methods in detail.

Recall from page 185 that the Java programming language allows you to overload methods by changing the number or the type of arguments to the method. You can also overload methods in a superclass. Here's an example of overloading the toString method:

public class MyClass { private int anInt = 4; //Overrides toString in Object class. public String toString() { return "Instance of MyClass. anInt = " + anInt; } //Overloads toString method name to provide additional functionality. public String toString(String prefix) { return prefix + ": " + toString(); } }

As illustrated by the preceding example, you might overload a superclass method to provide additional functionality. When writing a method that has the same name as a method in a superclass, double check the return values and the argument lists to make sure that you are overloading or overriding as you intended.

If a subclass defines a class method with the same signature as a class method in the superclass, the method in the subclass hides the one in the superclass. The distinction between hiding and overriding has important implications. Let's look at an example to see why. This example contains two classes. The first is Planet, [1] which contains one instance method and one class method:

[1] Planet.java is included on the CD and is available online. See Code Samples (page 224).

public class Planet { public static void hide() { System.out.println("The hide method in Planet."); } public void override() { System.out.println("The override method in Planet."); } }

The second class, a subclass of Planet, is called Earth: [2]

[2] Earth.java is included on the CD and is available online. See Code Samples (page 224).

public class Earth extends Planet { public static void hide() { System.out.println("The hide method in Earth."); } public void override() { System.out.println("The override method in Earth."); } public static void main(String[] args) { Earth myEarth = new Earth(); Planet myPlanet = (Planet)myEarth; myPlanet.hide(); myPlanet.override(); } }

The Earth class overrides the instance method in Planet called override and hides the class method in Planet called hide. The main method in this class creates an instance of Earth, casts it to a Planet reference, and then calls both the hide and the override methods on the instance. The output from this program is as follows:

The hide method in Planet. The override method in Earth.

The version of the hidden method that gets invoked is the one in the superclass, and the version of the overridden method that gets invoked is the one in the subclass. For class methods, the runtime system invokes the method defined in the compile-time type of the reference on which the method is called. In the example, the compile-time type of myPlanet is Planet. Thus, the runtime system invokes the hide method defined in Planet. For instance methods, the runtime system invokes the method defined in the runtime type of the reference on which the method is called. In the example, the runtime type of myPlanet is Earth. Thus, the runtime system invokes the override method defined in Earth.

An instance method cannot override a static method, and a static method cannot hide an instance method. Table 41 summarizes what happens when you define a method with the same signature as a method in a superclass.

Table 41. The effect of defining a method with the same signature as the superclass's method.

 

Superclass Instance Method

Superclass Static Method

Instance Method

Overrides (must also have the same return type)

Generates a compile-time error

Static Method

Generates a compile-time error

Hides

Hiding Member Variables

Within a class, a member variable that has the same name as a member variable in the superclass hides the superclass's member variable, even if their types are different. Within the subclass, the member variable in the superclass cannot be referenced by its simple name. Instead, the member variable must be accessed through super, which is covered in the next section. Generally speaking, we don't recommend hiding member variables.

Using super

If your method overrides one of its superclass's methods, you can invoke the overridden method through the use of super. You can also use super to refer to a hidden member variable. Consider this class, Superclass: [1]

[1] Superclass.java is included on the CD and is available online. See Code Samples (page 224).

public class Superclass { public boolean aVariable; public void aMethod() { aVariable = true; } }

Now, here's a subclass, called Subclass, [1] that overrides aMethod and hides aVariable:

[1] Subclass.java is included on the CD and is available online. See Code Samples (page 224).

public class Subclass extends Superclass { public boolean aVariable; //hides aVariable in Superclass public void aMethod() { //overrides aMethod in Superclass aVariable = false; super.aMethod(); System.out.println(aVariable); System.out.println(super.aVariable); } }

Within Subclass, the simple name aVariable refers to the one declared in SubClass, which hides the one declared in Superclass. Similarly, the simple name aMethod refers to the one declared in Subclass, which overrides the one in Superclass. So to refer to aVariable and aMethod inherited from Superclass, Subclass must use a qualified name, using super as shown. Thus, the print statements in Subclass's aMethod displays the following:

false true

You can also use super within a constructor to invoke a superclass's constructor. The following code sample is a partial listing of a subclass of Threada core class used to implement multitasking behaviorwhich performs an animation. The constructor for AnimationThread sets up some default values, such as the frame speed and the number of images, and then loads the images:

class AnimationThread extends Thread { int framesPerSecond; int numImages; Image[] images; AnimationThread(int fps, int num) { super("AnimationThread"); this.framesPerSecond = fps; this.numImages = num; this.images = new Image[numImages]; for (int i = 0; i <= numImages; i++) { ... // Load all the images. ... } } ... }

The line set in boldface is an explicit superclass constructor invocation that calls a constructor provided by the superclass of AnimationThread, namely, Thread. This particular Thread constructor takes a String that sets the name of Thread. If present, an explicit superclass constructor invocation must be the first statement in the subclass constructor: An object should perform the higher-level initialization first. If a constructor does not explicitly invoke a superclass constructor, the Java runtime system automatically invokes the no-argument constructor of the superclass before any statements within the constructor are executed.

Being a Descendant of Object

The Object [1] class sits at the top of the class hierarchy tree. Every class is a descendant, direct or indirect, of the Object class. This class defines the basic state and behavior that all objects must have, such as the ability to compare oneself to another object, to convert to a string, to wait on a condition variable, to notify other objects that a condition variable has changed, and to return the class of the object.

[1] http://java.sun.com/j2se/1.3/docs/api/java/lang/Object.html

The following is a list of handy methods that the Object class provides:

With the exception of notify, notifyAll, and wait, these methods are covered in the sections that follow. The notify, notifyAll, and wait methods all play a part in synchronizing the activities of independently running threads in a program. Refer to the chapter Threads: Doing Two or More Tasks at Once (page 269).

The clone Method

You use the clone method to create an object from an existing object. To create a clone, you write:

aCloneableObject.clone();

Object's implementation of this method checks to see whether the object on which clone was invoked implements the Cloneable interface. If the object does not, the method throws a CloneNotSupportedException. Even though Object implements the clone method, the Object class is not declared to implement the Cloneable interface, so classes that don't explicitly implement the interface are not cloneable. If the object on which clone was invoked does implement the Cloneable interface, Object's implementation of the clone method creates an object of the same type as the original object and initializes the new object's member variables to have the same values as the original object's corresponding member variables.

The simplest way to make your class cloneable, then, is to add implements Cloneable to your class's declaration. For some classes, the default behavior of Object's clone method works just fine. Other classes need to override clone to get correct behavior.

Consider a Stack class that contains a member variable referring to a Vector. If Stack relies on Object's implementation of clone, the original stack and its clone refer to the same vector. Changing one stack changes the other, which is undesirable behavior.

Here is an appropriate implementation of clone for our Stack class, which clones the vector to ensure that the original stack and its clone do not refer to the same vector:

public class Stack implements Cloneable { private Vector items; ... // code for Stack's methods and constructor not shown protected Object clone() { try { Stack s = (Stack)super.clone(); // clone the stack s.items = (Vector)items.clone();// clone the vector return s; // return the clone } catch (CloneNotSupportedException e) { //This shouldn't happen because //Stack and Vector are Cloneable. throw new InternalError(); } } }

The implementation for Stack's clone method is relatively simple. First, it calls Object's implementation of the clone method by calling super.clone, which creates and initializes a Stack object. At this point, the original stack and its clone refer to the same vector. Next, the method clones the vector.

Be Careful

The clone method should never use new to create the clone and should not call constructors. Instead, the method should call super.clone, which creates an object of the correct type and allows the hierarchy of superclasses to perform the copying necessary to get a proper clone.

 

The equals and hashCode Methods

The equals method compares two objects for equality and returns true if they are equal. The equals method provided in the Object class uses the identity operator (==) to determine whether two objects are equal. If the objects compared are the exact same object, the method returns true.

However, for some classes, two distinct objects of that type might be considered equal if they contain the same information. Consider this code that tests two Integers, one and anotherOne, for equality:

Integer one = new Integer(1), anotherOne = new Integer(1); if (one.equals(anotherOne)) { System.out.println("objects are equal"); }

This program displays objects are equal even though one and anotherOne reference two distinct objects. They are considered equal because the objects compared contain the same integer value.

You should override the equals method only if the identity operator is not appropriate for your class. If you override equals, override hashCode as well.

The value returned by hashCode is an int that maps an object into a bucket in a hash table. An object must always produce the same hash code. However, objects can share hash codes (they aren't necessarily unique). Writing a "correct" hashing function is easyalways return the same hash code for the same object. Writing an "efficient" hashing functionone that provides a sufficient distribution of objects over the bucketsis difficult and is outside the scope of this book.

Even so, the hashing function for some classes is relatively obvious. For example, an obvious hash code for an Integer object is its integer value. For an example of a class that overrides the equals and hashCode methods, see the BingoBall class in BINGO! [1]

[1] http://java.sun.com/docs/books/tutorial/together/index.html

The finalize Method

The Object class provides a method, finalize, that cleans up an object before it is garbage collected. This method's role during garbage collection was discussed previously, in the section Cleaning Up Unused Objects (page 129). The finalize method is called automatically by the system, and most classes do not need to override it. Thus, you can generally ignore this method.

The toString Method

The Object's toString method returns a String representation of the object. You can use toString along with System.out.println to display a text representation of an object, such as an instance of Double:

System.out.println(new Double(Math.PI).toString());

The String representation for an object depends entirely on the object. The String representation of a Double object is the double value displayed as text. Thus, the previous line of code displays 3.14159.

The toString method is very useful for debugging. You should override this method in all your classes.

The getClass Method

The getClass method returns a runtime representation of the class of an object. This method returns a Class object, which you can query for information about the class, such as its name, its superclass, and the names of the interfaces it implements. You cannot override getClass. The following method gets and displays the class name of an object:

void PrintClassName(Object obj) { System.out.println("The Object's class is " + obj.getClass().getName()); }

One handy use of a Class object is to create a new instance of a class without knowing what the class is at compile time. The following sample method creates a new instance of the same class as obj, which can be any class that inherits from Object:

Object createNewInstanceOf(Object obj) { return obj.getClass().newInstance(); }

If you already know the name of the class, you can also get a Class object from a class name. The following two lines are equivalent ways to get a Class object for the String class:

String.class [1] Class.forName("String")

[1] The type .class construct was added to the Java platform for the 1.1 release.

The first is more efficient than the second.

Writing Final Classes and Methods

Final Classes

You can declare that your class is final, that is, that your class cannot be subclassed. You might want to do this for two reasons: (1) to increase system security by preventing system subversion, and (2) for reasons of good object-oriented design.

  1. Security: One mechanism that hackers use to subvert systems is to create a subclass of a class and to then substitute the subclass for the original. The subclass looks and feels like the original class but does vastly different things, possibly causing damage or getting into private information. To prevent this kind of subversion, you can declare your class to be final and thereby prevent any subclasses from being created. The String class is a final class for just this reason. This class is so vital to the operation of the Java platform that it must guarantee that whenever a method or an object uses a String, it gets exactly a java.lang.String and not another kind of string. This ensures that all strings have no strange, inconsistent, undesirable, or unpredictable properties.

    If you try to compile a subclass of a final class, the compiler prints an error message and refuses to compile your program. In addition, the Java runtime system ensures that the subversion is not taking place at the bytecode level. It does this by checking to make sure that a class is not a subclass of a final class.

  2. Design: You may also wish to declare a class as final for object-oriented design reasons. You may think that your class is "perfect" or that, conceptually, your class should have no subclasses.

To specify that your class is final, use the keyword final before the class keyword in your class declaration. For example, if you wanted to declare your (perfect) ChessAlgorithm class as final, its declaration should look like this:

final class ChessAlgorithm { ... }

Any subsequent attempts to subclass ChessAlgorithm will result in a compiler error.

Final Methods

If declaring an entire class final is too heavy-handed for your needs, you can declare some or all of the class's methods final instead. Use the final keyword in a method declaration to indicate that the method cannot be overridden by subclasses. The Object class does this; some of its methods are final, and some are not.

You might wish to make a method final if it has an implementation that should not be changed and it is critical to the consistent state of the object. For example, instead of making your ChessAlgorithm class final, you might want the nextMove method to be final instead:

class ChessAlgorithm { ... final void nextMove(ChessPiece pieceMoved, BoardLocation newLocation) { ... } ... }

Writing Abstract Classes and Methods

Abstract Classes

Sometimes, a class that you define represents an abstract concept and, as such, should not be instantiated. Take, for example, food in the real world. Have you ever seen an instance of food? No. What you see instead are instances of carrot, apple, and chocolate chip cookies. Food represents the abstract concept of what we can eat. It doesn't make sense for an instance of food to exist.

Similarly, in object-oriented programming, you may want to model an abstract concept without being able to create an instance of it. For example, the Number class represents the abstract concept of numbers. It makes sense to model numbers in a program, but it doesn't make sense to create a generic number object. Instead, the Number class makes sense only as a superclass to such classes as Integer and Float, both of which implement specific kinds of numbers. A class such as Number, which represents an abstract concept and should not be instantiated, is called an abstract class. An abstract class can only be subclassed; it cannot be instantiated.

To declare that your class is an abstract class, use the keyword abstract before the class keyword in your class declaration:

abstract class Number { ... }

If you attempt to instantiate an abstract class, the compiler displays an error message.

Abstract Methods

An abstract class can contain abstract methodsmethods with no implementation. In this way, an abstract class can define a complete programming interface for its subclasses but allows its subclasses to fill in the implementation details of those methods. In practice, abstract classes provide a complete or partial implementation of at least one method. If an abstract class contains only abstract method declarations, it should be implemented as an interface instead. Interfaces are covered in the section Creating and Using Interfaces (page 228).

Let's look at an example of when you might want to create an abstract class with an abstract method in it. In an object-oriented drawing application, you can draw circles, rectangles, lines, Bézier curves, and so on. These graphic objects all have certain states (position, bounding box) and behaviors (move, resize, draw) in common. You can take advantage of these similarities and declare them all to inherit from the same parent objectfor example, GraphicObject, as shown in Figure 67.

Figure 67. Classes Rectangle, Line, Bezier, and Circle inherit certain states and behavior from their common parent, GraphicObject.

Graphic objects are substantially different in many ways: drawing a circle is quite different from drawing a rectangle. The graphic objects cannot share these types of states or behavior. On the other hand, all GraphicObjects must know how to draw themselves; they just differ in how they are drawn. This is a perfect situation for an abstract superclass.

First, you would declare an abstract class, GraphicObject, to provide member variables and methods that were wholly shared by all subclasses, such as the current position and the moveTo method. GraphicObject also declares abstract methods for methods, such as draw, that need to be implemented by all subclasses but that are implemented in entirely different ways (no default implementation in the superclass makes sense). The GraphicObject class would look something like this:

abstract class GraphicObject { int x, y; ... void moveTo(int newX, int newY) { ... } abstract void draw(); }

Each nonabstract subclass of GraphicObject, such as Circle and Rectangle, must provide an implementation for the draw method:

class Circle extends GraphicObject { void draw() { ... } } class Rectangle extends GraphicObject { void draw() { ... } }

An abstract class is not required to have an abstract method in it. But any class that has an abstract method in it or that does not provide an implementation for any abstract methods declared in its superclasses or implemented interfaces must be declared as an abstract class.

Summary of Managing Inheritance

Except for the Object class, a class has exactly one direct superclass. A class inherits member variables and methods from all its superclasses, whether direct or indirect. A subclass can override methods that it inherits, or it can hide variables or methods that it inherits. Table 41 on page 207 shows the effect of declaring a method with the same signature as a method in the superclass.

The Object class is the top of the class hierarchy. All classes are descendants from this class and inherit methods from it. Useful methods inherited from Object include toString, equals, clone, getClass, wait, notify, and notifyAll.

You can prevent a class from being subclassed by using the final keyword in the class's declaration. Similarly, you can prevent a method from being overridden by subclasses by declaring it as a final method.

An abstract class can only be subclassed; it cannot be instantiated. An abstract class can contain abstract methodsmethods that are declared but not implemented. Subclasses provide the implementations for abstract methods.

Questions and Exercises: Managing Inheritance

Questions

1:

Consider the following two classes:

public class ClassA { public void methodOne(int i) { } public void methodTwo(int i) { } public static void methodThree(int i) { } public static void methodFour(int i) { } } public class ClassB extends ClassA { public static void methodOne(int i) { } public void methodTwo(int i) { } public void methodThree(int i) { } public static void methodFour(int i) { } }

  1. Which method overrides a method in the superclass?
  2. Which method hides a method in the superclass?
  3. What do the other methods do?

2:

Consider the Card, Deck, and DisplayDeck classes you wrote for exercise 1 on page 203. What Object methods should each of these classes override?

Exercises

1:

Write the implementations for the methods that you answered in question 2.

2:

Write an abstract class. Write at least two of its nonabstract subclasses.

Answers

You can find answers to these Questions and Exercises online:

http://java.sun.com/docs/books/tutorial/java/javaOO/QandE/inherit-answers.html

Категории