Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

import java.*


November 1999/import java.*

Java supports polymorphism much as C++ does, but with a couple of interesting twists.


There is more to object-oriented programming than just objects. Although encapsulating data and functionality within a class boundary goes a long way to improve the organization of complex programs, the crowning feature of object orientation is polymorphism, which is the ability to process objects related by inheritance through a single abstract interface, while the behavior of those objects varies dynamically according to their actual type. In this article I'll discuss how to use inheritance and polymorphism in Java.

Code Sharing

The first benefit of inheritance that meets the eye is code economy. Consider a simple Employee class like that in Figure 1. The crucial operation here is computePay (just ask any employee), which in this case gives time-and-a-half for overtime. What should you do to handle salaried employees? If you were to define a SalariedEmployee class, it would look just like class Employee except for the name of the class (and its constructor, of course) and the implementation of computePay. You would have to duplicate the fields and other methods of Employee in SalariedEmployee, which is not only tedious but a maintenance headache, since both copies must be kept in sync. Inheritance allows you to define SalariedEmployee as an extension of Employee, as follows:

class SalariedEmployee
   extends Employee
{
   public SalariedEmployee
      (String name, double rate)
   {
      super(name, rate);
   }
   public double computePay()
   {
      return timeWorked * rate;
   }
}

All you have to do is say that SalariedEmployee (the "subclass") extends Employee (the "superclass"), and then specify what is different about SalariedEmployee. A SalariedEmployee object inherits everything else from Employee, so it has the name, rate, and timeWorked fields, and it can also call the other three Employee methods (getName, getRate, and recordTime). The SalariedEmployee constructor just passes its arguments to the Employee constructor to initialize its inherited fields. (That's what the super method does.) SalariedEmployee.computePay overrides Employee.computePay by interpreting timeWorked as the number of pay periods. Whenever you invoke computePay on a SalariedEmployee object, SalariedEmployee.computePay executes, not Employee.computePay.

Dynamic Binding

Another nice thing about inheritance is that it allows you to express class relationships in your code. In real life, a salaried employee is an employee, that is, anything you can say about employees also applies to salaried employees. The extends keyword in Java works analogously by implementing an is-a relationship between a subclass and its superclass: anywhere you can use an Employee object you can use a SalariedEmployee object. The program in Figure 2 illustrates this in two ways. First, notice that the program stores a handle to a SalariedEmployee object in an array of Employee. Since a SalariedEmployee is an Employee, this makes sense. Storing a handle to a subclass in a superclass handle is so common and crucial to object-oriented programming that it has a name: upcasting, so called because we usually draw class diagrams with superclasses positioned above subclasses (see Figure 3). The type of object a variable actually refers to at run time is called its dynamic type.

The second use of the is-a relationship is in the pay method. pay doesn't care whether the variable e refers to an instance of the superclass or the subclass. It treats its parameter as an Employee, and thus only calls methods named in the Employee class. Since a SalariedEmployee is an Employee, the computePay method applies. Butpay wants SalariedEmployee.computePay to execute, not Employee.computePay. How can this happen when e is declared as a handle to an Employee object?

The answer is dynamic binding, which maps a method name to an implementation according to the object's dynamic type. The expression e.computePay() will call the correct version of computePay according to whether e is referring to an Employee or a SalariedEmployee at the moment. The use of dynamic binding of methods in an inheritance hierarchy is called polymorphism.

Unless you specify otherwise, all methods in Java bind dynamically. A closer look at the definition of the Employee class suggests that we need a way to turn off dynamic binding, since functions like getName that only retrieve attributes aren't likely to vary. In fact, the only method that seems to need dynamic binding is computePay. To turn off dynamic method binding, use the final keyword, like this:

public final String getName()
{
   return name;
}
public final double getRate()
{
   return rate;
}
public final void
   recordTime(double time)
{
   timeWorked = time;
}

Final methods are just that — "final"; you cannot override them in subclasses. You can also declare a class final, which means that it can't be a superclass.

With a little imagination applied to this example you can appreciate the power of polymorphism. Consider a more complex payroll system in a separate module from the code that builds a heterogeneous collection of Employee objects. The payroll functions concern themselves only with Employee objects, and yet the right things happen because the methods called operate on the objects' dynamic type. If there is a change to computePay, the payroll code does not change. Furthermore, if you add a new type of employee, the payroll code still doesn't change! Dynamic binding takes care of invoking the right method. This perfect separation of interface and implementation known as polymorphism makes for clairvoyant code, as it were, because you can process objects of a type that doesn't even exist yet!

It is also possible to downcast, that is, to cast from a general type to a more specific type, but only if an object's dynamic type allows it. In Figure 4, for example, the dynamic type of e is Employee, so I can't use it as a SalariedEmployee. The instanceof operator is similar to C++'s dynamic_cast, and returns true if the dynamic type of the first operand is identical to or a subclass of the type specified in the second operand. If you try to downcast when you shouldn't you get a ClassCastException.

One caution in using inheritance is to be mindful of the access you give methods in a subclass. When you override an inherited method in a subclass you can increase its access (e.g., from private to public) but not decrease it. Otherwise you would destroy the ability of a subclass object to behave like its superclass. In other words, a subclass can provide more functionality, but not less. Likewise, a subclass can require less of its method's parameters, but not more. (For the more academically minded reader, the last two sentences describe covariance and contravariance, respectively.) In all ways a subclass instance should be substitutable for a superclass one.

The Mother of All Classes

When you define a class that doesn't extend another, your new class implicitly inherits from a class name Object, so all classes in Java are actually in one huge hierarchy with Object as the root. The Object class has a method named toString, which is called whenever an object needs to be represented as a string, such as when you pass it to System.out.println. Unless you override toString in your class, Object.toString gives you a string consisting of the class name, the @-sign, and the hash code of the object (a unique, system-generated integer), as the following example illustrates.

class MyClass
{}

class ToStringTest
{
   public static void
      main(String[] args)
   {
      MyClass m = new MyClass();
      System.out.println(m);
   }
}

/* Output:
MyClass@719aea8b
*/

Since toString is not a final method, however, you can override it to render a string of your choice, as in

class MyClass
{
   public String toString()
   {
      return "MyClassObject";
   }
}

The Java collection classes act as generic containers by holding instances of Object. The program in Figure 5 uses an ArrayList, a vector-like collection, to store some instances of Integer. In the call to add, the Integer object upcasts to an Object, since that is what ArrayList.add expects, and since everything, including Integer, is a subclass of Object. But to get an Integer back you need to explicitly cast the return value of ArrayList.get. If you try to cast to the wrong type you'll get a ClassCastException.

Abstract Classes

Sometimes a class at the top of a hierarchy represents a general concept that exists only for the purpose of unifying its subclasses. You can look at Employee in this way, in fact. That is, why is an hourly employee more of an employee than a salaried employee? Could I have just as easily used SalariedEmployee as the superclass? Of course. It makes more sense, then, to have a general Employee class and to derive both SalariedEmployee and HourlyEmployee and whatever else from it (see Figure 6).

Because there is no need to actually instantiate any Employee objects, Employee is called an abstract class. In Figure 7 I have redefined the Employee class with the abstract keyword, and have included everything that applies to all types of Employee objects, including a declaration of computePay as an abstract method. Because Employee is an abstract class, the Java compiler will not allow it to be the target of the new operator, so you can't have any Employee objects. Because computePay is abstract, in order to provide a concrete (i.e., instantiable) class, we must create a subclass, override computePay, and provide an implementation.

SalariedEmployee already fills this requirement, so its code doesn't change. The code for HourlyEmployee is in Figure 8, and the test program is in Figure 9. Any class with an abstract method is abstract, and must be so declared, although declaring a class abstract is sufficient to prohibit objects thereof, whether it has an abstract method or not. Abstract methods must not have a body, and any method without a body must be declared abstract.

Java vs. C++

As in C++, in Java you cannot access private superclass members in a subclass. I used package access in the employee classes, but if you wanted to access Employee members in a subclass defined in another package, I would have to declare those members protected.

A common error made by C++ novices is to override a function without making it virtual. The problem doesn't appear until you try to access objects through a pointer or reference, and the lack of dynamic binding may go undetected for some time. Java finesses this situation by making all methods virtual by default, and should you turn off dynamic binding by applying the final qualifier, you can no longer override that function. It is also a nice feature to be able to explicitly declare a class abstract, instead of requiring the presence of a pure virtual function to do it.

Another "gotcha" for C++ novices is to confuse overloading with overriding. In the following C++ code, for example, the function B::f is not found when searching for a function to match the expression d.f().

struct B
{
   virtual void f() {}
};

struct D : B
{
   void f(int) {}
};

int main()
{
   D d;
   d.f();     // error
   d.f(1);    // OK
}

A function in a derived class hides all functions of the same name in the base class, regardless of signature, because C++ follows the following scheme for name lookup:

1. Find a scope that contains the name of the function, starting in the current class, then going to any enclosing scopes if necessary.

2. Once the name is found, do overload resolution to find a matching function.

3. If a function was found, see if its access allows you to use it in the current context.

Since there is an f in D, step 1 is satisfied but step 2 fails, since there is no f() in D. Java, on the other hand, allows overloading and overriding to coexist (see Figure 10).

A C++ feature missing in Java is different types of inheritance. The extends keyword is all you get, and that means the same thing as public inheritance in C++ (i.e., "is-a" inheritance). It is nice to sometimes to use private inheritance for implementation reuse in C++, but the need for it is rare, and using an embedded object instead is a suitable workaround. I have never seen a need for protected inheritance in C++. (I think it's just there for theoretical reasons.)

Perhaps the biggest difference between Java and C++ with respect to objects is that Java does not support multiple inheritance — a class can extend only one class. At first blush this may seem quite restrictive, but while Java doesn't support multiple implementation inheritance, it does support multiple inheritance of interfaces. If you've ever had to plumb the depths of virtual base classes (in my opinion the darkest corner of C++), you might appreciate this restriction. In my next installment I'll cover interfaces in Java, but suffice it to say for now that a class in Java can implement any number of interfaces, which is the most common use of multiple inheritance in C++ anyway. Java attempts to provide support for object-oriented programming with a balance of safety, simplicity, and utility. It does a pretty good job.

Chuck Allison is a Senior Technical Advisor at Ingenix, a leading healthcare information company. He has been a contributing member of J16, the C++ standards committee, since 1991, and designed the Standard library's bitset class template. His book, C & C++ Code Capsules (Prentice-Hall, 1998), is filled with practical, ISO-compliant code. He is currently the Java columnist and consulting editor for the C/C++ Users Journal. Contact Chuck at [email protected].



Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.