Polymorphism

image

Polymorphism is a complicated name for a straightfoward concept. It is Greek for “many shapes,” and it means allowing the same code to be used with different types. In Java, it is implemented by allowing several different methods to have the same name, making it look like one method to the API user. “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 class wrapper for the primitive type int. The first method tries to interpret the String as an int, and return the corresponding binary int value. The second method does the same thing, but uses an arbitrary base that you specify. 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).”

Overriding

The second, more complicated kind of polymorphism, true polymorphism, is resolved dynamically at run-time. 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 superclass'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 w2 = new WarningWindow(f); // the only change
          w2.setSize(200,100);
          w2.setVisible(true);
          w2.setLocation(370,370);
}

The code creates two Window variables, w and w2. The first variable, w, is assigned a Window and various properties are set. The second Window variable, w2, is assigned a WarningWindow. The same properties are set (it's put in a slightly different location, so it doesn't obscure the first Window).

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 8-5. Wow!

Figure 8-5. A Window variable invokes setSize() but ends up bigger! It is actually pointing to a variable of subclass WarningWindow which overrides Window.setSize() with its own version.

image
image

When we invoke the method on something that started out as a general Window, but may have been assigned a WarningWindow at run-time, the correct method is chosen at run-time 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 fundamentally changes what it does. You 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 run-time” 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. If you provide your own version of toString() for one of your classes, that will be used instead of the Object toString(), even if you are accessing your object through an Object pointer. To see this in action, add some code to Timestamp to print its values like “12:15:03”, e.g.

class Timestamp {
    int hrs;
    int mins;
    int secs;

    public String toString() {
        String result = "" + hrs;
        String pad = ":";
        if (mins<10) pad = ":0";
        result += pad + mins;
        if (secs<10) pad = ":0";
        else pad = ":";
        result += pad + secs;
        return result;
    }
    /* constructor and more code omitted */
}

Then put two lines like this in a method that will be executed.

Object o = new Timestamp();
System.out.println("Object o is " + o );

You will see output like this, confirming that polymorphism and late binding are taking place:

Object o is 19:03:17

Defining toString for your classes can also be used to provide debugging output.

Forcing overriding off: final

We have already seen final applied to data to make it constant. There are two further adjustments to fine-tune inheritance of methods: abstract and final. You will be able to use these when you get more practiced in defining type hierarchies.

Let's consider the class java.lang.Math that provides various trig and math operations.

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 { /*more code*/
    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. It's another reason why you should always invoke class methods using the name of the class, not an instance of it: to remind yourself that the method will not be overridden.

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. 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 hint 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 performance reasons. Even if a method is not inlined, the method call can be made much more quickly if the compiler can compile the call directly, rather than have the run-time system figure out during execution what the actual class is, and hence which is the right overriding method. The 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. String objects are read-only. The class does not provide any public methods to change characters in the middle of an existing String. If you could override String, you could write a subclass that was not read-only, and that also 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.

Private methods are all implicitly final—because no one outside the class can see them, no one can override them.

Forcing overriding on: 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 require 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.

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. 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 indicating an abstract class probably made no sense at all, so I'll show what it means in terms of an example. We will use java.awt.Component as an example of an abstract class. 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, Component meets the first condition of abstract classes: it has 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 a Component object on the screen (even if you could instantiate it, 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 still incomplete in some way.”

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

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

Some methods in Component are concrete. These are the methods that set various flags and keep track of options, like the current position of the Component on the screen and whether it has been made visible or not. Some methods of Component are abstract, like anything to do with drawing its appearance on the screen. The subclasses of Component will implement the DrawMeOnScreen() and other abstract methods in a way that makes sense for them. Button will implement it so that it draws a rounded rectangle with a label. Scrollbar will implement it so it draws an “elevator bar” and associated arrow icons.

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

public class Scrollbar extends Component {
     public void DrawMeOnScreen(){ /*more code*/ }
}

The last piece of the picture is some other class entirely that wants to operate on any of the Component subclasses. It treats them all as a Component, and when it needs the component to be drawn, it can call its DrawMeOnScreen() method, and be assured that one exists.

public class Container {
     public void remove(Component comp) { /*code*/ }
     public Component add(Component comp){ comp.DrawMeOnScreen(); }
}

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—paintComponent(), if you must know. We'll meet it in a later chapter.

We could get by without abstract. Polymorphism would still take place, and overriding methods called. Abstract provides a way for library writers to force programmers to implement certain methods. The use of extends is at least twenty times more common than the use of abstract to fine-tune class hierarchies in the Java run-time library.

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

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