Chapter 6. More OOP—Extending Classes

More OOP—Extending Classes

We now come to the second part of object-oriented programming where we cover another two parts of “A PIE:” inheritance and polymorphism. You need a solid understanding of these to successfully use the Java library routines. Despite the unusual names, they describe some clean concepts.

Inheritance means basing a new class on a class that is already defined to extend it in some way. Inheritance is what you acquire from past generations. 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. For example, the implicit parent if you don’t name one is java.lang.Object.

class A { ... } 

actually means:

class A extends java.lang.Object { ... } 

The class java.lang.Object is the root of all classes. The keyword extends followed by the name of some other class is how you indicate that the other class is the parent class of this class, also known as a superclass. All the non-private members of the superclass are available in a subclass just as though they were declared directly in the subclass. Some terminology:

class

A data type.

extend

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

superclass

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

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

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 6-1.

A real-world example of an inheritance hierarchy.

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

In biology, animals are classified by whether they have a spinal cord or not. 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. 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.

We can also show how inheritance applies in theory to the Java primitive types, but that model only goes so far. Although byte is a subset or subtype of short, and short is a subtype of int, that makes them assignment-compatible and that’s about it. An important part of OOP is figuring out and advantageously using the hierarchies of the abstract data types in your application. That doesn’t really work on the primitives in Java, so we have to leave them behind in our examples at this point.

To summarize, inheritance occurs when a class adopts or adapts the data structures and methods of a base or parent class. That creates a hierarchy, similar to a scientific taxonomy. Each level is a specialization of the one above. 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 menubar, so you can’t move it or close it using a mouse. The source for the Java runtime library is part of the JDK, and you can look at Window.java at $JAVAHOME/src/java/awt/Window.java. If you look at the Window source file, you’ll see the following code:

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, 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. Similarly, our window has no title bar or border, so you can’t grab it with the mouse. If you compile and run the code, you will get the window shown in Figure 6-2.

Window and Frame.

Figure 6-2. Window and Frame.

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 possible ways to implement WarningWindow:

  1. Change the Window class and add a constructor for a special Window that is colored red. This is a bad approach because you never, ever want to change the standard runtime library, even if you get the source for it.

  2. 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.

  3. 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 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 to 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); 
    } 
} 

We have to add a constructor anyway because Windows take a Frame argument in their constructor, so we can’t rely on the default no-arg constructor. We write a WarningWindow constructor with the Frame argument, have it 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. As you can see, all the non-private members of the superclass 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); 
    } 
} 

We can call the three setSomething() methods, even though we didn’t declare them in WarningWindow. We inherited them from Window.

Try running the program and you will see the result shown in Figure 6-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.

Program results: A regular window and a WarningWindow.

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

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 runtime 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 a class to create essentially a new library, but 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.

Take another look at the constructor in the WarningWindow class. That line of code that reads super(anyFrame) is a common idiom when one class extends another. The code super( ) is the way you express “call the constructor of my superclass.” That’s exactly what we want here: a regular Window is constructed, and its color is changed to red.

“Is a” vs. “Has a”

As we saw in Chapter 2, a superclass constructor is always invoked when you create a subclass object. If you don’t explicitly call a superclass constructor, then the no-arg constructor of the superclass is called for you. If the superclass doesn’t have a no-arg constructor (either an implicit one because you didn’t provide any constructors or an explicit no-arg constructor that you did provide), then you will get a compilation error along the lines of “no constructor found in superclass.”

The most common use of super is the call super() which invokes a constructor in the superclass. The keyword is also used to access fields of any superclass (not just the immediate superclass) that are hidden by an identically-named feature in the subclass.

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.

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 Names Collide?

If a field in the subclass has the same name as a field in the superclass, then it supersedes, or 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. Don’t hide names as a general practice.

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( " cast = " + ((Fruit) c).zestySkin ); 
     } 
} 

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

f.zesty = false 
c.zesty = true 
cast = 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.

A variable 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 runtime 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 the section on overriding later in this chapter.

It turns out that all objects carry around a little bit of extra information about their type and the characteristics of that type. The runtime 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 at the end of this chapter.

Compatibility of Superclass and Subclass

One of the nice 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.

This is a 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 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 { ... } 
Mammal m; 
Dog fido = new Dog(); 
Cat kitty = new Cat(); 
     ... m = fido; 

Notice the assignment of fido (a Dog object) into m (a Mammal variable). 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. You can’t directly assign the following:

fido = m;    // causes compilation error 

You can, however, cast it. 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 runtime 

Type hierarchies are often drawn as tree diagrams with Object (the ultimate superclass) at the top, and all subclasses beneath their superclass as Figure 6-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.

An inheritance hierarchy may be many levels deep.

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

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 runtime. If the parent is referring to the correct child type, the assignment will occur. If the parent refers 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.

You can probably guess how the instanceof operator is used. We met instanceof in the last chapter. It allows you to compare a reference variable with a type, and it returns a value at runtime based on what type of object the variable is truly pointing to at that moment.

Mammal m; 
if (Math.random() < 0.5) m = new Dog(); else m = new Mammal(); 
if (m instanceof Dog) // Check the type before attempting cast 
     fido = (Dog) m; // the cast will always succeed. 

Polymorphism

Polymorphism

Polymorphism is a complicated name for a straightfoward concept. It is Greek for “many shapes,” and it merely means using the same one name to refer to different methods. “Name reuse” would be a better term. There are two types of polymorphism in Java: the really easy kind (overloading) and the interesting kind (overriding).

Overloading

The really easy kind of polymorphism is called overloading in Java and other languages, and it means that in any class you can use the same name for several different (but hopefully related) methods. The methods must have different signatures, however, so that the compiler can tell which of the synonyms is intended. Here are two overloaded methods:

public static int parseInt(String s) throws NumberFormatException 
public static int parseInt(String s, int radix) 
                                   throws NumberFormatException 

These methods come from the class java.lang.Integer, which is a class wrapper [2] for the primitive type int and has some helper functions like these. The first method tries to interpret the String as an int. The second method does the same thing, but uses an arbitrary base. You could parse a hexadecimal String by supplying 16 as the radix argument.

The return type and the exceptions that a method can throw are not looked at when resolving same-named functions in Java.

The I/O facilities of a language are one typical place where overloading is used. You don’t want to have an I/O class that requires a different method name depending on whether you are trying to print a short, an int, a long, and so on. You just want to be able to say “print(thing).” Note that C fails to meet this requirement. Although it’s the same routine, “printf,” it also needs a format specifier (which is a statement in a particularly ugly programming language in its own right) to tell printf what argument types to expect and to output. If you change the type of the C value you are outputting, you usually need to change the format specifier, too.

Overriding

The second, more complicated kind of polymorphism, true polymorphism, is resolved dynamically at runtime. It occurs when a subclass class has a method with the same signature (number, type, and order of parameters) as a method in the superclass. When this happens, the method in the derived class overrides the method in the superclass. Methods cannot be overridden to be more private only to be more public.

An example should make this clear. Let’s go back to our base class Window, and our subclass WarningWindow. I happen to know that one of the operations you can do with a Window is setSize() on it. That’s even in our example program. We will give WarningWindow its own version of setSize() to reflect the fact that WarningWindows are more important and should be bigger than regular Windows. We add a setSize() method to our subclass:

class WarningWindow extends java.awt.Window {

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

    public void setSize(int x, int y) { // overriding method 
        int bigx = (int) (x*1.5); 
        int bigy = (int) (y*1.5); 
        super.setSize(bigx, bigy); 
    } 
} 

The method setSize() in WarningWindow replaces or overrides the super-class’s version of setSize() when the method is invoked on a WarningWindow object. C++ programmers will note that you do not need to specifically point out to the compiler (with the C++ “virtual” keyword) that overriding will take place. Here’s some example code:

public static void main(String args[]) {
        Frame f = new Frame(); 
        { Window w = new Window(f); 
          w.setSize(200,100); 
          w.setVisible(true); 
          w.setLocation(300,300); 
        } 
        { Window w = new WarningWindow(f); // the only change 
          w.setSize(200,100); 
          w.setVisible(true); 
          w.setLocation(370,370); 
        } 
} 

I have simply duplicated some code and put it in separate blocks (separate scopes) so that the duplication of the variable w doesn’t cause a compiler error. In the first block, I assign a regular window to w; in the second, I assign a WarningWindow. Even though w remains a Window object, different methods are called on it.

When you try running this, you will note that when we apply the setSize() method to a Window, we get the base class version, meaning it comes out the regular size. When we apply the setSize method to a WarningWindow, we get the WarningWindow specialized version which it displays 50% bigger in each direction as shown in Figure 6-5. Wow!

WarningWindow invokes setSize() but ends up bigger!

Figure 6-5. WarningWindow invokes setSize() but ends up bigger!

WarningWindow invokes setSize() but ends up bigger!

When we invoke the method on something that started out as a general Window, but may have been assigned a WarningWindow at runtime, the correct method is chosen at runtime based on what the object actually is. And that is polymorphism. It is a powerful tool for letting a class implement an operation in a way that is unique to itself.

It would clearly be bad design to override a method in a way that fundamen-tally changes what it does. You probably wouldn’t really want to make setSize() use different values from its arguments. It makes a great demonstration for teaching purposes, though, because the difference is so visible.

The technical term for “choosing the correct method for whatever object this is at runtime” is late binding or delayed binding. Polymorphism is the language feature that allows two methods to have the same name, such that late binding may be applied.

Constructor declarations are not members. They are never inherited and therefore are not subject to hiding or overriding.

Inheriting from Object

So the meaning of inheritance is that a class has access to the members of a parent class. If a class has its own version of a method in a parent class, its own version will be called.

This has implications for program maintenance. When you see a class accessing some member, if you cannot find the declaration in the class itself, look in the parent class, and then in the parent of the parent class, all the way back to the ultimate base class Object, if necessary.

Inheritance is the reason why you can call toString() on any object. If you look back at the listing of Object at the end of Chapter 3, you’ll see it has a number of methods that are inherited by every class. The method toString() is one of these.

Forcing Overriding off: Final

There are two further adjustments to fine-tune inheritance: abstract and final. You will be able to use this to great advantage when you become more expert. Let’s go back to the class java.lang.Math that we met at the end of Chapter 5. One feature of the class is that all trig operations are done in radians, not degrees. Would it be possible to extend java.lang.Math and override just the trig functions so that they worked with degrees instead? You could leave all the other rounding and comparing Math functions alone, and you would have specialized the subclass for your needs.

Your code might look like this:

public class DegreeMath extends java.lang.Math { // won’t work 
     public double sin(double d) {
          double rads = super.toRadians(d); 
          double result = super.sin(rads); 
          return super.toDegrees(result); 
     } 
... 
} 

That is a great idea in theory, but it cannot be done this way for the Math class in practice for two reasons. One, the Math class is labeled as final, so cannot be extended. Two, all the Math methods are static.

public final class Math { ... 
     public static native double sin(double a); 

Static methods do not participate in overriding. Overriding is used when an object might be one type or might be a more specialized subtype. Class methods don’t belong to an object and never have this possible ambiguity. Acting as a reminder that a method will not be overridden is another reason why you should always invoke class methods using the name of the class, not an instance of it.

When the keyword final appears at the start of a class declaration, it means “no one can extend this class.” Similarly, an individual method can be made final, preventing it from being overridden when its class is inherited. It is final in the sense that it is a leaf of an inheritance tree. Typically, you might wish to prevent further inheritance to avoid further specialization or for security reasons: you don’t want to permit this type to be further adjusted. A “final” method is also a clue to the compiler to inline the code. Inlining the code means optimizing out the overhead of a method call by taking the statements in the body of the method and duplicating them inline instead of making the call. This is a classic space versus time trade-off.

The class java.lang.Math is labeled as final for reasons of performance. Overriding can be “turned off” on a method or class by using the keyword final on a method or class. A method call can be made much more quickly if the compiler can compile it in, rather than having the runtime system figure out which is the right overriding method at execution time. (This performance cost is the reason overriding is off by default in C++.)

The class java.lang.String is labeled as final for reasons of security and performance. As well as being a final class, String objects are read-only. If you could override String, you could write a subclass that was not read-only, but could be used in all the places that String is used. Specifically, you could change the String pathname to a file after it had been checked for access permission, but before the open operation had been done.

Forcing Overriding: Abstract

Just as final tells the compiler “this thing is complete and must not be changed or extended or overridden,” there is a keyword to force overriding to take place. The keyword abstract tells the compiler “this thing is incomplete and must be extended to be used.” You can think of final and abstract as opposites of each other, as they cannot occur together for a method or class. The keyword final or abstract can be applied to an individual method or an entire class. We have already seen final applied to data.

When the keyword abstract appears at the start of a class declaration, it means that zero or more of its methods are abstract. An abstract method has no body; its purpose is to force some subclass to override it and provide a concrete implementation of it. Labeling a method abstract requires you to label the class abstract.

You make a class abstract when three conditions are fulfilled:

  • There will be several subclasses.

  • You want to handle all the different subclasses as an instance of the superclass.

  • The superclass alone does not make sense as an object.

That set of conditions for deciding when to use an abstract class probably made no sense at all, so I’ll try to show what it means in terms of an example. Think back to the GUI class Window. We showed a few pages back how Window is a subclass of Component (not directly, but two levels down). It turns out that Component is the window toolkit superclass for many Java library objects that you can display on the screen. In particular, many screen widgets (Unix terminology) or controls (Microsoft terminology) are Components. Scrollbars, panels, dialogs, tabbed panes, cursors, labels, textfields, and so on are all subclasses of Component. Thus, it meets the first condition of abstract classes: there is more than one subclass.

There are many operations that we can do on a control without precisely knowing or caring which control it is. One common operation is to add it to a container for display. A container is a GUI backdrop or pinboard whose purpose is to arrange and display the group of components it is given. We don’t want to have umpteen individual methods—one for adding each individual type of control to a panel—we just want to be able to say the following:

myContainer.add(Component c); 

We have the second condition: for convenience, you want to handle all the different subclasses as an instance of the superclass. The most frequently seen case is where the superclass is a parameter to a method, as it is here.

Finally, all the subclasses of Component are real, concrete objects. You can actually display them on the screen. Component itself is not a concrete object. You cannot sensibly display the Component superclass on the screen (what would be drawn?). Only the subclasses have the actual semantics of shape, behavior, and look. An instance of Component itself doesn’t really make sense. You need an instance of a concrete subclass of Component. In this way, the third condition has been met: an instance of the superclass does not make sense as an object. Hence, java.awt.Component is an abstract class.

Although making a class abstract forces a programmer to extend it and fill in some more details in the subclass before it can be instantiated, it allows the class to stand in for any of the concrete subclasses. Saying a class is abstract imposes the requirements “you must implement the abstract method(s) in a subclass” and “you cannot instantiate an object of the abstract class, it is incomplete in some way.” Abstract has no connection with data abstraction we saw in Chapter 2.

Here’s the general form that Component has in source code:

public abstract class Component {
     public void setBackground(java.awt.Color){...} 
     public void setVisible(boolean) { ...} 
     public void setLocation(int, int){ ...} 
     ... // 3800 lines of code omitted 
     public abstract void DrawMeOnScreen(); 
} 


// some concrete subclasses of Component 
public class Button extends Component {
     public void DrawMeOnScreen(){ ...} 
} 

public class Scrollbar extends Component { ... 
     public void DrawMeOnScreen(){...} 
} 



// Some other class entirely that wants to operate 
// on any of the above subclasses 
public class Container {
     public void remove(Component comp) { ...} 
     public Component add(Component comp){ comp.DrawMeOnScreen(); } 
     void setFocusOwner(Component c) { .. } 
} 

I have fudged a bit on the name and use of DrawMeOnScreen() to make the example simpler. That’s what the method does, but it is called something less intuitive— paint(), if you must know. We’ll meet it in a later chapter.

The use of extends is at least twenty times more common than the use of abstract to fine-tune class hierarchies in the Java runtime library.

The Class Whose Name Is Class

Here’s where the terminology admittedly can get a little confusing. We saw a few pages back that every object has some Run Time Type Information associated with it. The RTTI for any object is stored in an object whose class is called “Class.” Class could certainly use a better, less self-referential name.

A big use of Class is to help with loading new classes during program execution. A lot of other languages load an entire program before they start execution. Java encourages dynamic loading. Classes only need to be loaded on demand when they are first referenced.

When you compile the class Fruit, it creates the bytecode file Fruit.class. Fruit.class contains the bytecode for Fruit. When the first reference to a Fruit type or object is encountered at runtime, the JVM asks the java.lang.ClassLoader class to find and load that class file. Typically, the ClassLoader transforms the fully qualified name into a file name and looks for that in certain places locally and across the net. Class objects are created automatically by the JVM as part of loading the class. Class provides the information for checking casts at runtime, and it lets a program look at all the members of a class—the data fields and the methods. These are systems programming activities, unlikely to occur in your programs, but providing Class makes it easy to program them without stepping outside the Java system. You can safely skim over the rest of this section and return when you have a specific need to look at RTTI.

To access the runtime type information for a class, you need an object of type Class. You can get one in three ways. Here’s some code showing the alternatives:

Fruit lemon = new Fruit(); 

Class which = lemon.getClass(); // getClass is a method in Object 

or

Class which = Class.forName("Fruit"); // forName is a static method 

or

Class which = Fruit.class; // class literal 

The last alternative is called a class literal. You can jam the characters .class onto the end of any type at all, even a primitive type like int, and it gets you the Class RTTI associated with the type. You’ll choose which of the alternatives to use depending on whether you have an object, the classname in a String, or the class.

Once you have the class object you can invoke its methods. The strict left-to-right evaluation of Java allows method calls to be chained together in one statement. You can do this with any methods where the result of one method is the reference used in the next:

String name = myobject.getClass().getName(); 

The class whose name is Class looks like this:

public final class java.lang.Class  {
     public java.io.InputStream getResourceAsStream(java.lang.String); 
     public java.net.URL getResource(java.lang.String); 

     public native String getName(); 
     public static native java.lang.Class forName(java.lang.String); 
     public native java.lang.Object newInstance(); 

     static native Class getPrimitiveClass(java.lang.String); 

     public native boolean isInstance(java.lang.Object); 
     public native boolean isAssignableFrom(java.lang.Class); 
     public native boolean isInterface(); 
     public native boolean isArray(); 
     public native boolean isPrimitive(); 

// security related 
     public native ClassLoader getClassLoader(); 
     public native Object getSigners()[]; 
     native void setSigners(java.lang.Object[]); 

// introspection on the class, its ancestors, members and constructors 
     public native Class getSuperclass(); 
     public native Class getInterfaces()[]; 
     public native Class getComponentType(); 
     public native int getModifiers(); 
     public Class getDeclaringClass(); 
     public Class getDeclaredClasses()[]; 
     public Class getClasses()[]; 
     public reflect.Constructor getConstructors()[]; 
     public reflect.Constructor getConstructor(java.lang.Class[]); 
     public reflect.Constructor getDeclaredConstructors()[]; 
     public reflect.Constructor getDeclaredConstructor(java.lang.Class[]); 

     public reflect.Field getFields()[]; 
     public reflect.Field getField(java.lang.String); 
     public reflect.Field getDeclaredFields()[]; 
     public reflect.Field getDeclaredField(java.lang.String); 

     public reflect.Method getMethods()[]; 
     public reflect.Method getMethod(java.lang.String, java.lang.Class[]); 
     public reflect.Method getDeclaredMethods()[]; 
     public reflect.Method getDeclaredMethod(
                              java.lang.String, java.lang.Class[]); 

     public java.lang.String toString(); 
} 

It can be useful to print out the names of classes while debugging code that deals with arbitrary objects. A description of this and other popular methods of Class follow. The first returns the name of the Class.

public native String getName(); 

The next method takes a String that should be the name of a class and retrieves the Class (RTTI) object for that class. It’s an alternative to getClass(), used when you have the name of the class in a String, rather than having any objects of it.

public static native Class forName(String className) 
                           throws ClassNotFoundException 

This next example is a surprising method—it allows you to create an object of the class for which this is the RTTI. Coupled with the forName() method, this lets you create an object of any class whose name you have in a String. Highly dynamic! The no-arg constructor of the appropriate class is called, so it better have one.

public native Object newInstance() 
throws InstantiationException, IllegalAccessException 

In the following example, if you have an instance of a class, and you cast it to the class that you know it is, you can call its methods!

String s = "Fruit"; 
     ... 
Object f = Class.forName(s).newInstance(); 

Use the classloader that loaded this next class to get a resource (e.g., a file) from the same place. That place might be a zip or Jar file, a local filesystem, or a network connection.

public InputStream getResourceAsStream(String name) 

Similar to the previous method, this one returns a URL that can access the resource rather than a Stream with its contents.

public java.net.URL getResource(String name) {
        name = resolveName(name); 

This checks if the Class it is invoked on is the same as, or a superclass of, the obj. This is the dynamic equivalent of the instanceof operator. If true, it means you could assign the obj, possibly with a cast, into the object for whom this is the RTTI.

public native boolean isInstance(Object obj); 

Exercises

  1. What are the four attributes that distinguish object-oriented programming? What are some advantages and disadvantages of OOP?

  2. Give three examples of primitive types and three examples of predefined Java classes (i.e., object types).

  3. What is the default constructor, and when is it called? What is a no-arg constructor?

  4. Describe overriding, and write some code to show an example.

  5. Consider the following three related classes:

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

    There are these variables of each class:

    Mammal m; 
    Dog d = new Dog( ); 
    Cat c = new Cat( ); 

    Which of these statements will cause an error at compile time and why? Which of these statements may cause an error at runtime and why?

    m = d; // 1. 
    d = m; // 2. 
    d = (Dog) m; // 3. 
    d = c; // 4. 
    d = (Dog) c; // 5. 
  6. Create a class that provides all the same methods as java.lang.Math, but which operate on degrees not radians. Do this by creating a wrapper for each method in Math, in your class.

Some Light Relief—The Nerd Detection System

Most people are familiar with the little security decals that electronic and other high-value stores use to deter shoplifters. The sticker contains a metallic strip. Unless deactivated by a store cashier, the sticker sets off an alarm when carried past a detector at the store doors.

These security stickers are actually a form of antenna. The sticker detector sends out a weak RF signal between two posts through which shoppers will pass. It looks for a return signal at a specific frequency, which indicates that one of the stickers has entered the field between the posts.

All this theory was obvious to a couple of California Institute of Technology students Dwight Berg and Tom Capellari, who decided to test the system in practice. Naturally, they selected a freshman to (unknowingly) participate in the trials. At preregistration, after the unlucky frosh’s picture was taken but before it was laminated onto his I.D. card, Dwight and Tom fixed a couple of active security decals from local stores onto the back of the photo.

The gimmicked card was then laminated together, hiding the McGuffin, and the two conspirators awaited further developments. A couple of months later they caught up with their victim as he was entering one of the stores. He was carrying his wallet above his head. In response to a comment that this was an unusual posture, the frosh replied that something in his wallet, probably his bank card, seemed to set off store alarms. He had been conditioned to carry his wallet above his head after several weeks of setting off the alarms while entering and leaving many of the local stores.

The frosh seemed unimpressed with Dwight and Tom’s suggestion that perhaps the local merchants had installed some new type of nerd detection system. Apparently, the comment got the frosh thinking, because on the next occasion when he met Dwight he put him in a headlock until he confessed to his misdeed.

Moral: Never annoy a computer programmer.



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

[2] A class wrapper is a class wrapped around a primitive type so you can treat that primitive value as an object and send it to data structures and methods that process only objects.

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

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