Chapter 8. More OOP—Extending Classes

  • Inheritance

  • Polymorphism

  • The Class Whose Name Is Class

  • Exercises

  • Some Light Relief—The Nerd Detection System

We now come to the main part of object-oriented programming, covering inheritance and polymorphism. Despite the unusual names, they describe some clean concepts. You need a solid understanding of inheritance and polymorphism to program in an object-oriented language and to use some of the Java library routines.

As we have seen several times, inheritance means basing a new class on a class that is already defined. The new class will extend the existing class in some way. Just as inheritance in real life is “what you get from a parent,” inheritance in OOP is “what you get from a parent class.” Every class has one immediate parent class. This is either a parent that you name explicitly or the parent that you get implicitly. The implicit parent you get if you don't name one explicitly is java.lang.Object.

class A { /*code*/ }

is equivalent to:

class A extends java.lang.Object { /*code*/ }

The class java.lang.Object is the ultimate parent class of all classes. The phrase "class A extends B" is how you indicate that A is a child of class B. A child class is also known as a subclass or subtype. A subclass can access all the non-private members of its superclass/supertype just as though they were declared directly in the subclass.

Table 8-1 provides some terminology.

Table 8-1. Object-oriented programming terminology

Term

Definition

class

A data type.

extend

To make a new class that inherits the contents of an existing class.

superclass or supertype

A parent or “base” class. Superclass wrongly suggests that the parent class has more than the subclass. It means “super” in the sense of “above.” The superclass is above its children in the inheritance tree.

subclass or subtype

A child class that inherits, or extends, a superclass. It is called a subclass because it only represents a subpart of the universe of things that make up the superclass. It usually has more fields to represent its specialization, however.

Inheritance

In this section we are going to look at a real-world example of type inheritance, and turn that into code fragments to give you a solid understanding of the big picture. Then we'll look at a more substantial example of inheritance in OOP, and present that in terms of a complete program you can type in and run.

Real-world example of inheritance

To see what inheritance means in practice, consider a real-world example of the Linnaean taxonomy of the animal kingdom, as shown in Figure 8-1.

Figure 8-1. A real-world example of an inheritance hierarchy

image

Animals are classified by whether they have a spinal cord or not. Developing the example with code, we have:

class Animal {   }
class Chordata extends Animal { final boolean spine=true; }

All mammals have a spinal cord. They inherit it as a characteristic because they are a subclass of the chordata phylum (fancy words for “the group with spines”). Mammals, like humans, also have specialized characteristics: they feed their young milk, they have hair, they have two generations of teeth, and so on.

class Mammal extends Chordata { final int teeth_gens = 2; }

Primates inherit all the characteristics of mammals, including the quality of having a spinal cord, which mammals inherited from their parent type. The primate subclass is specialized with forward facing eyes, a large braincase, and so on to increasingly specialized subtypes.

class Primate extends Mammal { final String braincaseSize = "large"; }

An important part of OOP is figuring out and advantageously using the inheritance hierarchies of the class data types. Inheritance is one of the concepts people mean when they say object-oriented programming requires a special way of thinking. Get ready to spring forward with that “conceptual leap.”

A Java example of inheritance

There is a GUI library class in Java called Window that implements the simplest kind of window. The class Window doesn't even have borders or a titlebar, so you can't move it or close it using a mouse. If you look at the src/java/awt/Window.java source file, you'll see code similar to the following (slightly simplified for clarity):

package java.awt;
import several-packages;
public class Window {
    // about 900 lines of code of methods and fields

    public Window(Frame owner) { /*a constructor*/ }
}

Read the code carefully on this and the following pages, as we're going to stick with this example for much of the chapter.

A Window object can be moved or written on by your code and can hold other GUI objects. Here's a program to instantiate a Window object and display it on the screen:

import java.awt.*;
public class example {
    public static void main(String args[]) {
        Frame f = new Frame();// Window must belong to a Frame
        Window w = new Window(f);
        w.setSize(200,100);
        w.setVisible(true);
        w.setLocation(50,50);
    }
}

The constructor for Window requires a more fully featured GUI object with a title bar, namely a Frame, so we create one here just to give it to the constructor. We don't do anything else with the Frame. We don't even bother making it visible. If you compile and run the code, you will get the window shown in Figure 8-2.

Figure 8-2. Window and Frame

image

For the sake of this inheritance example, let's assume that your program needs a slightly different kind of window: a WarningWindow. The only change from Window is that you want WarningWindows to be colored red to highlight their importance. Everything else should work exactly like Window and should have all the same methods and fields.

There are at least three alternative ways to implement WarningWindow:

  • Change the Window class and add a constructor for a special Window that is colored red. This is a bad approach because application programmers should never, ever change the standard run-time library, even if you do have the source for it.

  • Copy all the code in Window.java into file WarningWindow.java, making the change you need. This is a bad approach because it is impossible to keep duplicated code in synchronization. Whenever the code for class Window changes in future releases, class WarningWindow will be out of sync and may well stop working.

  • Make WarningWindow a subclass of Window, so it inherits all the functionality from Window. Add a small amount of code for the different behavior you want.

The preferred OOP approach is the third one: make WarningWindow extend the class Window so that WarningWindow inherits all the data and methods of Window.

class WarningWindow extends java.awt.Window {
    // put any additional or changed members in here
}

This is exactly how the OOP process is supposed to work: Find a class that does most of what you want, and then subclass it to provide the exact functionality. There's nothing special about the libraries that come with Java. You are supposed to subclass system classes and your own classes as needed.

There are two points to watch here. First (unlike real life), the child class chooses its parent class. The parent has some say in the matter in that it can control its visibility with access modifiers, and it can make itself final to say “no class is permitted to extend me.” Second, you have to know what a class does and how it is implemented in order to extend it successfully. Despite the goals of encapsulation, you cannot treat a superclass like a black box and completely ignore its inner workings. This is because a subclass is essentially a statement that says, “I belong inside the same black box as the superclass.”

I happen to know that the Window class, like many graphical classes, has a method called setBackground() that can be used to change the color of its background.[1] All we have to do is make sure that every WarningWindow calls that method when it is being instantiated. A good way to ensure that is to put the call in a constructor. The code should go in a file called WarningWindow.java, as follows:

class WarningWindow extends java.awt.Window {

    WarningWindow(java.awt.Frame anyFrame) { //a constructor
        super(anyFrame);
        setBackground(java.awt.Color.red);
    }
}

The class java.awt.Window does not have a default no-arg constructor. So we need to explicitly call one of the constructors in Window when our subclass is instantiated. We write a WarningWindow constructor that takes a Frame parameter, call the constructor of its superclass (that's the super(anyFrame) statement), and then call setBackground() to set the window color to red.

Here's an example program that instantiates a regular window and a WarningWindow. We can call the three setSomething() methods, even though we didn't declare them in WarningWindow. We inherited them from Window. All the non-private members of the chain of superclasses (stretching back to Object) are available in the subclass just as if they were declared directly in the subclass.

import java.awt.*;
public class example { // example use of 2 kinds of Window

    public static void main(String args[]) {
        Frame f = new Frame();

        Window w = new Window(f); // standard Window
        w.setSize(200,100);
        w.setVisible(true);
        w.setLocation(300,300);

        // The new red-hued WarningWindow we created
        WarningWindow ww = new WarningWindow(f);
        ww.setSize(200,100); // setSize is in a superclass
        ww.setVisible(true);
        ww.setLocation(370,370);
    }
}

Put example.java in the same directory with WarningWindow.java and compile them. Try running the program and you will see the result shown in Figure 8-3. Since the red window won't show up red in a printed book, you're going to have to try it to prove that I'm not kidding you.

Figure 8-3. Program results: A regular window and a WarningWindow

image

Choosing the right parent saves a lot of work

That's your first example of inheritance. We have reused the functionality of 900 lines of code in a new six-line class that is in a separate file and only contains the differences and specializations from its superclass. How is this any different from a library? What we have here is a powerful way to take an existing library and modify some of its behavior without modifying the library itself.

You might ask how this is any different from instantiating an ordinary Window and always remembering to color it red. The answer is that inheritance lets the compiler and run-time do the hard work of keeping track of what an object is, and whether it has the library behavior or the modified behavior. Inheritance lets you superimpose some new behavior on an existing class, creating a new class without copying everything. I use this all the time when debugging or prototyping some code. “What if I did it this way?” I ask myself, and I write a subclass that extends the original class and contains my experimental changes. If the idea is bad, I just throw away the new class, and I haven't changed one line of source code in the underlying class.

The super keyword is not really very super

The most common use of super is the call super() which invokes a constructor in the superclass, as in the WarningWindow class above.

The keyword can also be used to access fields of any superclass (not just the immediate superclass) that are hidden or shadowed by an identically named field in the subclass. Similarly the super keyword can be used to invoke overridden methods in the superclass. Neither of these techniques is very common.

There is no way to “chain” several supers together, however, and reach back higher into the parent class hierarchy. Do not think that because super.x means “the x of a superclass” therefore “super.super.x” means “the x of grandparent.” This is a very common mistake. There is no super.super.x. Super doesn't work that way at all. The right way to think of super is as a reference to the current object, but typed as an instance of its superclass. This is weird and goes against the OOP philosophy that you chose methods and fields based on the actual type of an object, not the type of any reference to it. Super is a “safety hatch” so you can get hold of parent members if absolutely necessary.

Inheritance usually provides increasing specialization as you go from a general superclass class (e.g., vehicle) to a more specific subclass (e.g., passenger car, fire truck, or delivery van). It can equally well restrict or extend the available operations, though.

What happens when data field names collide?

It's bad style to give a data field in a subclass the same name as a field in a superclass. If you do this, the subclass field hides the superclass field. The visible subclass field is said to shadow (put in the shade) the superclass field. The superclass field is still available by using the super keyword. It is also available if you assign the subclass to the superclass type. Be careful here.

class Fruit {       // example of variable name hiding
     boolean zestySkin= false;
}

public class Citrus extends Fruit {
     boolean zestySkin= true;   // same name hides the Fruit one

     public static void main (String args[]) {
          Citrus c = new Citrus();
          Fruit f = c; // f and c now refer to the same object
          System.out.println( " f.zesty = " + f.zestySkin );
          System.out.println( " c.zesty = " + c.zestySkin );
          System.out.println( " Parent cast sees child field = "
                                    + ((Fruit) c).zestySkin );
     }
}

When you run the above code, you get this output:

f.zesty = false
c.zesty = true
Parent cast sees child field = false // surprise!

Be sure to note that a cast of something to its superclass makes it see the superclass variables where there is name hiding. The reason Java allows name duplication is to permit new fields to be added later to superclasses without breaking existing subclasses that might already use that name. The subclasses will continue to use their own copies of those fields, except when you tell them to behave like superclass objects. Methods are intended for overloading, but not fields. Don't hide field names as a general practice.

A data field may have the same name as a method in its own class or superclass without either hiding the other.

class Example {
     public int total = 22;   // overloading field and method is dumb,
     public int total () {    // but it works OK

Name duplication should be rare, because the Java Language Specification says that method names should be based on verbs, while field names should be based on nouns (JLS, sect. 6.8).

In the case of a method with the same name in both a superclass and the subclass, the run-time system figures out exactly what class this object really is and calls the method that is a member of that particular class. This is dealt with in olymorphism on page 171.

It turns out that all objects carry around a little bit of extra information about their type and the characteristics of that type. The run-time system needs to keep track of the type of an object reference to check casts to invoke the right version of overloaded methods. The information is known as Run Time Type Information (RTTI), and it is kept in an object of its own. You get to the RTTI object through the getClass() method that is in class Object and thus inherited by every class. The type of an RTTI object is a reference type whose name is Class. That class called Class is featured in The Class Whose Name Is ClassClass on page 179.

Objects keep their identity, regardless of reference type!

One of the great things about inheritance is that it lets you treat a specialized object as a more general one. In other words, my WarningWindow, by virtue of being a subclass of Window, counts as a Window and can be used anywhere in code where a Window is used. If you have a method that expects a Window as an argument, you can pass it a WarningWindow, and it will work fine.

If you have a Window variable, you can assign a WarningWindow into it, like this:

WarningWindow ww = new WarningWindow( new Frame() );
Window w = ww; // the Window obj now refers to a WarningWindow

Here's the really magical thing: that Window object will continue to behave as a WarningWindow! It will display with a red background whenever someone invokes a Window operation on it that causes it to be updated on the screen. Even though we access the object using a Window pointer, the object knows it is a Warning Window and still behaves as one.

This is the key point of OOP. When you have a variable of SomeClass, it might actually be referring to a much more specialized subclass. If a method takes some particular superclass type as a parameter, you can actually call it with any of its subclasses and it will do the right thing. Add some lines of code to the example class a few pages back to try this. You can even assign a WarningWindow object to an Object, then later cast it back to a Window or WarningWindow, and it won't have lost its type information.

Casting

Let's look a little more closely into compatibility between subclass and superclass. We'll use the following code for examples:

public class Mammal {  }
public class Dog extends Mammal {   }
public class Cat extends Mammal {   }

Dog fido = new Dog();
Cat kitty = new Cat();
Mammal m = fido;

Notice the assignment of m (a Mammal variable) = fido (a Dog object) . You can always make a more general object hold a more specialized one, but the reverse is not true without an explicit type conversion. All dogs are mammals, but not all mammals are dogs. Cats, horses, pigs, and people are also mammals. You can assign m=fido, but not (directly) fido=m, because m could be referring to a Cat object.

Just as you can cast (convert) an integer into a double, you can cast a superclass into one of its more specialized subclasses. To cast any type, write the typename in parentheses immediately before the object being cast. In this case:

fido = (Dog) m;    // The cast allows the compilation to work
                   // and the conversion is checked at run-time

Type hierarchies are often drawn as tree diagrams with Object (the ultimate superclass) at the top, and all subclasses beneath their superclass as Figure 8-4 exemplifies. In a drawing of this kind, you can only cast “downward” from a superclass to some subclass (or subclass of a subclass, and so on). You can never cast “sideways” to force an object to become something it is not.

Figure 8-4. An inheritance hierarchy may be many levels deep

image

The general rules for casting classes are:

  • You can always assign parent = child; a cast is not needed because a specific child class also belongs to its general parent class. You can assign several levels up the hierarchy; that is, the parent may be a more remote ancestor. Chordata c = new Dog() is valid.

  • You can cast child = (child) parent, and it will be checked at run-time. If the parent is referring to the correct child type, the assignment will occur. If the parent is actually pointing to some unrelated subclass, an exception ClassCastException will be raised. Exceptions are a recoverable interruption to the normal flow of control. They are described later.

  • You cannot assign or cast at all between arbitrary unrelated classes, as in fido=kitty;

Because every class is a subclass of the built-in system class Object, every object can be assigned to something of type Object, and later cast back to the type that it really is. In this way, the type Object can be used as a general reference to anything.

Some Java utility classes store and manipulate Object. You can use them for any object, later casting to get back the same type that you put in. You can be certain that, if a cast succeeds, you really have an object of the type you cast to. This is another illustration that there is no evading strong typing in Java.


[1] Actually, setBackground() and many of the other “Window” routines really come from a parent of Window; in this case, the class called Component.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.239.103