Extending Classes

   

Core Java™ 2: Volume I - Fundamentals
By Cay S. Horstmann, Gary Cornell
Table of Contents
Chapter 5.  Inheritance


Let's return to the Employee class that we discussed in the previous chapter. Suppose (alas) you work for a company at which managers are treated differently than other employees. Managers are, of course, just like employees in many respects. Both employees and managers are paid a salary. However, while employees are expected to complete their assigned tasks in return for receiving their salary, managers get bonuses if they actually achieve what they are supposed to do. This is the kind of situation that cries out for inheritance. Why? Well, you need to define a new class, Manager, and add functionality. But you can retain some of what you have already programmed in the Employee class, and all the fields of the original class can be preserved. More abstractly, there is an obvious "is a" relationship between Manager and Employee. Every manager is an employee: this "is a" relationship is the hallmark of inheritance.

Here is how you define a Manager class that inherits from the Employee class. You use the Java keyword extends to denote inheritance.

 class Manager extends Employee {    added methods and fields } 

graphics/cplus_icon.gif

Inheritance is similar in Java and C++. Java uses the extends keyword instead of the : token. All inheritance in Java is public inheritance; there is no analog to the C++ features of private and protected inheritance.

The keyword extends indicates that you are making a new class that derives from an existing class. The existing class is called the superclass, base class, or parent class. The new class is called the subclass, derived class, or child class. The terms superclass and subclass are those most commonly used by Java programmers, although some programmers prefer the parent/child analogy, which also ties in nicely with the "inheritance" theme.

The Employee class is a superclass, but not because it is superior to its subclass or contains more functionality. In fact, the opposite is true: subclasses have more functionality than their superclasses. For example, as you will see when we go over the rest of the Manager class code, the Manager class encapsulates more data and has more functionality than its superclass Employee.

graphics/notes_icon.gif

The prefixes super and sub come from the language of sets used in theoretical computer science and mathematics. The set of all employees contains the set of all managers, and this is described by saying it is a superset of the set of managers. Or, put it another way, the set of all managers is a subset of the set of all employees.

Our Manager class has a new field to store the bonus, and a new method to set it:

 class Manager extends Employee {    . . .    public void setBonus(double b)    {       bonus = b;    }    private double bonus; } 

There is nothing special about these methods and fields. If you have a manager object, you can simply apply the setBonus method.

 Manager boss = . . .; boss.setBonus(5000); 

Of course, if you have an Employee object, you cannot apply the setBonus method it is not among the methods that are defined in the Employee class.

However, you can use methods such as getName and getHireDay with Manager objects. Even though these methods are not explicitly defined in the Manager class, they are automatically inherited from the Employee superclass.

Similarly, the fields name, salary, and hireDay are inherited from the superclass. Every Manager object has four fields: name, salary, hireDay and bonus.

When defining a subclass by extending its superclass, you only need to indicate the differences between the subclass and the superclass. When designing classes, you place the most general methods into the superclass, and more specialized methods in the subclass. Factoring out common functionality by moving it to a superclass is very common in object-oriented programming.

However, some of the superclass methods are not appropriate for the Manager subclass. In particular, the getSalary method should return the sum of the base salary and the bonus. You need to supply a new method to override the superclass method:

 class Manager extends Employee {    . . .    public double getSalary()    {       . . .    }    . . . } 

How can you implement this method? At first glance, it appears to be simple: Just return the sum of the salary and bonus fields:

 public double getSalary() {    return salary + bonus; // won't work } 

However, that won't work. The getSalary method of the Manager class has no direct access to the private fields of the superclass. This means that the getSalary method of the Manager class cannot directly access the salary field, even though every Manager object has a field called salary. Only the methods of the Employee class have access to the private fields. If the Manager methods want to access those private fields, they have to do what every other method does use the public interface, in this case, the public getSalary method of the Employee class.

So, let's try this again. You need to call getSalary instead of simply accessing the salary field.

 public double getSalary() {    double baseSalary = getSalary(); // still won't work    return baseSalary + bonus; } 

The problem is that the call to getSalary simply calls itself, because the Manager class has a getSalary method (namely the method we are trying to implement). The consequence is an infinite set of calls to the same method, which leads to a program crash.

We need to indicate that we want to call the getSalary method of the Employee superclass, not the current class. You use the special keyword super for this purpose: The call

 super.getSalary() 

calls the getSalary method of the Employee class. Here is the correct version of the getSalary method for the Manager class:

 public double getSalary() {    double baseSalary = super.getSalary();    return baseSalary + bonus; } 

graphics/notes_icon.gif

Some people think of super as being analogous to the this reference. However, that analogy is not quite accurate super is not a reference to an object. For example, you cannot assign the value super to another object variable. Instead, super is a special keyword that directs the compiler to invoke the superclass method.

As you saw, a subclass can add fields, and it can add or override methods of the superclass. However, inheritance can never take away any fields or methods.

graphics/cplus_icon.gif

Java uses the keyword super to call a superclass method. In C++, you would use the name of the superclass with the :: operator instead. For example, the getSalary method of the Manager class would call Employee::getSalary instead of super.getSalary.

Finally, let us supply a constructor.

 public Manager(String n, double s, int year, int month, int day) {    super(n, s, year, month, day);    bonus = 0; } 

Here, the keyword super has a different meaning. The instruction

 super(n, s, year, month, day); 

is shorthand for "call the constructor of the Employee superclass with n, s, year, month, and day as parameters."

Since the Manager constructor cannot access the private fields of the Employee class, it must initialize them through a constructor. The constructor is invoked with the special super syntax. The call using super must be the first statement in the constructor for the subclass.

If the subclass constructor does not call a superclass constructor explicitly, then the superclass uses its default (no-parameter) constructor. If the superclass has no default constructor and the subclass constructor does not call another superclass constructor explicitly, then the Java compiler reports an error.

graphics/notes_icon.gif

Recall that the this keyword has two meanings: to denote a reference to the implicit parameter, and to call another constructor of the same class. Likewise, the super keyword has two meanings: to invoke a superclass method, and to invoke a superclass constructor. When used to invoke constructors, the this and super keywords are closely related. The constructor calls can only occur as the first statement in another constructor. The construction parameters are either passed to another constructor of the same class (this) or a constructor of the superclass (super).

graphics/cplus_icon.gif

In a C++ constructor, you do not call super, but you use the initializer list syntax to construct the superclass. The Manager constructor looks like this in C++:

 Manager::Manager(String n, double s, int year, int month,    int day) // C++ : Employee(n, s, year, month, day) {    bonus = 0; } 

Having redefined the getSalary method for Manager objects, managers will automatically have the bonus added to their salaries.

Here's an example of this at work: we make a new manager and set the manager's bonus:

 Manager boss = new Manager("Carl Cracker", 80000,    1987, 12, 15); boss.setBonus(5000); 

We make an array of three employees:

 Employee[] staff = new Employee[3]; 

We populate the array with a mix of managers and employees:

 staff[0] = boss; staff[1] = new Employee("Harry Hacker", 50000,    1989, 10, 1); staff[2] = new Employee("Tony Tester", 40000,    1990, 3, 15); 

We print out everyone's salary:

 for (int i = 0; i < staff.length; i++) {    Employee e = staff[i];    System.out.println(e.getName() + " "       + e.getSalary());; } 

This loop prints the following data:

 Carl Cracker 85000.0 Harry Hacker 50000.0 Tommy Tester 40000.0 

Now staff[1] and staff[2] each print their base salary because they are Employee objects. However, staff[0] is a Manager object and its getSalary method adds the bonus to the base salary.

What is remarkable is that the call

 e.getSalary() 

picks out the correct getSalary method. Note that the declared type of e is Employee, but the actual type of the object to which e refers can be either Employee (that is, when i is 1 or 2) or Manager (when i is 0).

When e refers to an Employee object, then the call e.getSalary() calls the getSalary method of the Employee class. However, when e refers to a Manager object, then the getSalary method of the Manager class is called instead. The virtual machine knows about the actual type of the object to which e refers, and therefore can invoke the correct method.

The fact that an object variable (such as the variable e) can refer to multiple actual types is called polymorphism. Automatically selecting the appropriate method at runtime is called dynamic binding. We will discuss both topics in more detail in this chapter.

graphics/cplus_icon.gif

In Java, you do not need to declare a method as virtual. Dynamic binding is the default behavior. If you do not want a method to be virtual, you tag it as final. (We discuss the final keyword later in this chapter.)

Example 5-1 contains a program that shows how the salary computation differs for Employee and Manager objects.

Example 5-1 ManagerTest.java
  1. import java.util.*;  2.  3. public class ManagerTest  4. {  5.    public static void main(String[] args)  6.    {  7.       // construct a Manager object  8.       Manager boss = new Manager("Carl Cracker", 80000,  9.          1987, 12, 15); 10.       boss.setBonus(5000); 11. 12.       Employee[] staff = new Employee[3]; 13. 14.       // fill the staff array with Manager and Employee objects 15. 16.       staff[0] = boss; 17.       staff[1] = new Employee("Harry Hacker", 50000, 18.          1989, 10, 1); 19.       staff[2] = new Employee("Tommy Tester", 40000, 20.          1990, 3, 15); 21. 22.       // print out information about all Employee objects 23.       for (int i = 0; i < staff.length; i++) 24.       { 25.          Employee e = staff[i]; 26.          System.out.println("name=" + e.getName() 27.             + ",salary=" + e.getSalary()); 28.       } 29.    } 30. } 31. 32. class Employee 33. { 34.    public Employee(String n, double s, 35.       int year, int month, int day) 36.    { 37.       name = n; 38.       salary = s; 39.       GregorianCalendar calendar 40.          = new GregorianCalendar(year, month - 1, day); 41.          // GregorianCalendar uses 0 for January 42.       hireDay = calendar.getTime(); 43.    } 44. 45.    public String getName() 46.    { 47.       return name; 48.    } 49. 50.    public double getSalary() 51.    { 52.       return salary; 53.    } 54. 55.    public Date getHireDay() 56.    { 57.       return hireDay; 58.    } 59. 60.    public void raiseSalary(double byPercent) 61.    { 62.       double raise = salary * byPercent / 100; 63.       salary += raise; 64.    } 65. 66.    private String name; 67.    private double salary; 68.    private Date hireDay; 69. } 70. 71. class Manager extends Employee 72. { 73.    /** 74.       @param n the employee's name 75.       @param s the salary 76.       @param year the hire year 77.       @param month the hire month 78.       @param day the hire day 79.    */ 80.    public Manager(String n, double s, 81.       int year, int month, int day) 82.   { 83.       super(n, s, year, month, day); 84.       bonus = 0; 85.    } 86. 87.    public double getSalary() 88.    { 89.       double baseSalary = super.getSalary(); 90.       return baseSalary + bonus; 91.    } 92. 93.    public void setBonus(double b) 94.    { 95.       bonus = b; 96.    } 97. 98.    private double bonus; 99. } 

Inheritance Hierarchies

Inheritance need not stop at deriving one layer of classes. We could have an Executive class that derives from Manager, for example. The collection of all classes extending from a common superclass is called an inheritance hierarchy, as shown in Figure 5-1. The path from a particular class to its ancestors in the inheritance hierarchy is its inheritance chain.

Figure 5-1. Employee inheritance hierarchy

graphics/05fig01.gif

There is usually more than one chain of descent from a distant ancestor class. You could derive a Programmer class from Employee class or a Secretary class from Employee, and they would have nothing to do with the Manager class (or with each other). This process can continue as long as is necessary.

Polymorphism

There is a simple rule to know whether or not inheritance is the right design for your data. The "is-a" rule states that every object of the subclass is an object of the superclass. For example, every manager is an employee. Thus, it makes sense for the Manager class to be a subclass of the Employee class. Naturally, the opposite is not true not every employee is a manager.

Another way of formulating the "is-a" rule is the substitution principle. That principle states that you can use a subclass object whenever the program expects a superclass object.

For example, you can assign a subclass object to a superclass variable.

 Employee e; e = new Employee(. . .);  // Employee object expected e = new Manager(. . .); // OK, Manager can be used as well 

In the Java programming language, object variables are polymorphic. A variable of type Employee can refer to an object of type Employee or an object of any subclass of the Employee class (such as Manager, Executive, Secretary, and so on).

We took advantage of this principle in Example 5-1:

 Employee[] staff = new Employee[3]; Manager boss = new Manager(. . .); staff[0] = boss; 

In this case, the variables staff[0] and boss refer to the same object. However, staff[0] is considered to be only an Employee object by the compiler.

That means, you can call

 boss.setBonus(5000); // OK 

but you can't call

 staff[0].setBonus(5000); // ERROR 

The declared type of staff[0] is Employee, and the setBonus method is not a method of the Employee class.

However, you cannot assign a superclass reference to a subclass variable. For example, it is not legal to make the assignment:

 Manager m = staff[i]; // ERROR 

The reason is clear: Not all employees are managers. If this assignment were to succeed and m were to refer to an Employee object that is not a manager, then it would later be possible to call m.setBonus(...), and a runtime error would occur.

graphics/cplus_icon.gif

Java does not support multiple inheritance. (For ways to recover much of the functionality of multiple inheritance, see the section on Interfaces in the next chapter.)

Dynamic Binding

It is important to understand what happens when a method call is applied to an object. Here are the details:

  1. The compiler looks at the declared type of the object and the method name. Let's say we call x.f(args), and the implicit parameter x is declared to be an object of class C. Note that there may be multiple methods, all with the same name f, but with different parameter types. For example, there may be a method f(int) and a method f(String). The compiler enumerates all methods called f in the class C and all public methods called f in the superclasses of C.

    Now the compiler knows all possible candidates for the method to be called.

  2. Next, the compiler determines the types of the parameters that are supplied in the method call. If among all the methods called f there is a unique method whose parameter types are a best match for the supplied parameters, then that method is chosen to be called. This process is called overloading resolution. For example, in a call x.f("Hello"), the compiler picks f(String) and not f(int). The situation can get complex because of type conversions (int to double, Manager to Employee, and so on). If the compiler cannot find any method with matching parameter types, or if there are multiple methods that all match after applying conversions, then the compiler reports an error.

    Now the compiler knows the name and parameter types of the method that needs to be called.

    graphics/notes_icon.gif

    Recall that the name and parameter type list for a method is called the method's signature. For example, f(int) and f(String) are two methods with the same name but different signatures. If you define a method in a subclass that has the same signature as a superclass method, then you override that method. However, the return type is not part of the signature. Therefore, you cannot define a method int f(String) in a superclass and a method void f(String) in a subclass.

  3. If the method is private, static, final, or a constructor, then the compiler knows exactly which method to call. (The final modifier is explained in the next section.) This is called static binding. Otherwise, the method to be called depends on the actual type of the implicit parameter, and dynamic binding must be used at run time. In our example, the compiler would generate an instruction to call f(String) with dynamic binding.

  4. When the program runs and uses dynamic binding to call a method, then the virtual machine must call the version of the method that is appropriate for the actual type of the object to which x refers. Let's say the actual type is D, a subclass of C. If the class D defines a method f(String), that method is called. If not, D's superclass is searched for a method f(String), and so on.

    It would be time-consuming to carry out this search every time a method is called. Therefore, the virtual machine precomputes a method table for each class that lists all method signatures and the actual methods to be called. When a method is actually called, the virtual machine simply makes a table lookup. In our example, the virtual machine consults the method table for the class D and looks up the method to call for f(String). That method may be D.f(String) or X.f(String), where X is some superclass of D .

    There is one twist to this scenario. If the call is super.f(args), then the compiler consults the method table of the superclass of the implicit parameter.

Let's look at this process in detail in the call e.getSalary() in Example 5-1. The declared type of e is Employee. The Employee class has a single method called getSalary, and there are no method parameters. Therefore, in this case, we don't worry about overloading resolution.

Since the getSalary method is not private, static, or final, it is dynamically bound. The virtual machine produces method tables for the Employee and Manager classes. The Employee table shows that all methods are defined in the Employee class itself:

 Employee:    getName() -> Employee.getName()    getSalary() -> Employee.getSalary()    getHireDay() -> Employee.getHireDay()    raiseSalary(double) -> Employee.raiseSalary(double) 

Actually, that isn't quite true as you will see later in this chapter, the Employee class has a superclass Object from which it inherits a number of methods. We ignore the Object methods for now.

The Manager method table is slightly different. Three methods are inherited, one method is redefined and one method is added.

 Manager:    getName() -> Employee.getName()    getSalary() -> Manager.getSalary()    getHireDay() -> Employee.getHireDay()    raiseSalary(double) -> Employee.raiseSalary(double)    setBonus(double) -> Manager.setBonus(double) 

At run time, the call e.getSalary() is resolved as follows.

  1. First, the virtual machine fetches the method table for the actual type of e. That may be the table for Employee, Manager, or another subclass of Employee.

  2. Then, the virtual machine looks up the defining class for the getSalary() signature. Now it knows which method to call.

  3. Finally, the virtual machine calls the method.

Dynamic binding has a very important property: it makes programs extensible without the need for recompiling existing code. Suppose a new class Executive is added, and there is the possibility that the variable e refers to an object of that class. The code containing the call e.getSalary() need not be recompiled. The Executive.getSalary() method is called automatically if e happens to refer to an object of type Executive.

graphics/caution_icon.gif

When you override a method, the subclass method must be at least as visible as the superclass method. In particular, if the superclass method is public, then the subclass method must also be declared as public. It is a common error to accidentally omit the public specifier for the subclass method. Then the compiler complains that you try to supply a weaker access privilege.

Preventing Inheritance: Final Classes and Methods

Occasionally, you want to prevent someone from deriving a class from one of your classes. Classes that cannot be extended are called final classes, and you use the final modifier in the definition of the class to indicate this. For example, let us suppose we want to prevent others from subclassing the Executive class. Then, we simply declare the class using the final modifier as follows:

 final class Executive extends Manager {    . . . } 

You can also make a specific method in a class final. If you do this, then no subclass can override that method. (All methods in a final class are automatically final.) For example,

 class Employee {    . . .    public final String getName()    {       return name;    }    . . . } 

graphics/notes_icon.gif

Recall that fields can also be declared as final. A final field cannot be changed after the object has been constructed. However, if a class is declared as final, only the methods, not the fields, are automatically final.

You will want to make a class or method final for one of two reasons:

  1. Efficiency

    Dynamic binding has more overhead than static binding thus, programs with dynamic calls run slower. More importantly, the compiler cannot replace a trivial method with inline code because it is possible that a subclass would override that trivial code. The compiler can put final methods inline. For example, if e.getName() is final, the compiler can replace it with e.name.

    CPUs hate procedure calls because procedure calls interfere with their strategy of getting and decoding the next instructions while processing the current one. Replacing calls to trivial procedures with inline code is a big win. Naturally, this is an issue for a compiler, not for a bytecode interpreter.

  2. Safety

    The flexibility of the dynamic dispatch mechanism means that you have no control over what happens when you call a method. When you send a message, such as e.getName(), it is possible that e is an object of a subclass that redefined the getName method to return an entirely different string. By making the method final, you avoid this possible ambiguity.

For example, the String class is a final class. That means, nobody can define a subclass of String, and the compiler and virtual machine can optimize calls to String methods.

graphics/cplus_icon.gif

In C++, a method is not dynamically bound by default, and you can tag it as inline to have method calls replaced with the method source code. However, there is no mechanism that would prevent a subclass from overriding a superclass method. In C++, it is possible to write classes from which no other class can derive, but it requires an obscure trick, and there are few reasons to do so. (The obscure trick is left as an exercise to the reader. Hint: Use a virtual base class.)

Casting

Recall from Chapter 3 that the process of forcing a conversion from one type to another is called casting. The Java programming language has a special notation for casts. For example:

 double x = 3.405; int nx = (int)x; 

converts the value of the expression x into an integer, discarding the fractional part.

Just as you occasionally need to convert a floating-point number to an integer, you also need to convert an object reference from one class to another. To actually make a cast of an object reference, you use a syntax similar to what you use for casting a numeric expression. Surround the target class name with parentheses and place it before the object reference you want to cast. For example:

 Manager boss = (Manager)staff[0]; 

There is only one reason why you would want to make a cast to use an object in its full capacity after its actual type has been temporarily forgotten. For example, in the Manager class, the staff array had to be an array of Employee objects since some of its entries were regular employees. We would need to cast the managerial elements of the array back to Manager to access any of its new variables. (Note that in the sample code for the first section, we made a special effort to avoid the cast. We initialized the boss variable with a Manager object before storing it in the array. We needed the correct type to set the bonus of the manager.)

As you know, in Java, every object variable has a type. The type describes the kind of object the variable refers to and what it can do. For example, staff[i] refers to an Employee object (so it can also refer to a Manager object).

You rely on these descriptions in your code, and the compiler checks that you do not promise too much when you describe a variable. If you assign a subclass object to a superclass variable, you are promising less, and the compiler will simply let you do it. If you assign a superclass object to a subclass variable, you are promising more, and you must confirm that you mean what you say to the compiler with the (Subclass) cast notation.

What happens if you try to cast down an inheritance chain and you are "lying" about what an object contains?

 Manager boss = (Manager)staff[1]; // ERROR 

When the program runs, the Java runtime system notices the broken promise, and generates an exception. If you do not catch the exception, your program terminates. Thus, it is good programming practice to find out whether a cast will succeed before attempting it. Simply use the instanceof operator. For example:

 if (staff[1] instanceof Manager) {    boss = (Manager)staff[1];    . . . } 

Finally, the compiler will not let you make a cast if there is no chance for the cast to succeed. For example, the cast

 Date c = (Date)staff[1]; 

is a compile-time error because Date is not a subclass of Employee.

To sum up:

  • You can cast only within an inheritance hierarchy.

  • Use instanceof to check before casting from a superclass to a subclass.

graphics/notes_icon.gif

The test

 x instanceof C 

does not generate an exception if x is null. It simply returns false. That makes sense. Since null refers to no object, it certainly doesn't refer to an object of type C.

Actually, converting the type of an object by performing a cast is not usually a good idea. In our example, you do not need to cast an Employee object to a Manager object for most purposes. The getSalary method will work correctly on both types because the dynamic binding that makes polymorphism work locates the correct method automatically.

The only reason to make the cast is to use a method that is unique to managers, such as setBonus. If you for some reason find yourself wanting to call setBonus on Employee objects, ask yourself whether this is an indication of a design flaw in the superclass. It may make sense to redesign the superclass and add a setBonus method. Remember, it takes only one bad cast to terminate your program. In general, it is best to minimize the use of casts and the instanceof operator.

Casts are commonly used with generic containers such as the ArrayList class, which will be introduced later in this chapter. When retrieving a value from a container, its type is known only as the generic type s, and you must use a cast to cast it back to the type of the object that you inserted into the container.

graphics/cplus_icon.gif

Java uses the cast syntax from the "bad old days" of C, but it works like the safe dynamic_cast operation of C++. For example,

 Manager boss = (Manager)staff[1]; // Java 

is the same as

 Manager* boss = dynamic_cast<Manager*>(staff[1]); // C++ 

with one important difference. If the cast fails, it does not yield a null object, but throws an exception. In this sense, it is like a C++ cast of references. This is a pain in the neck. In C++, you can take care of the type test and type conversion in one operation.

 Manager* boss = dynamic_cast<Manager*>(staff[1]); // C++ if (boss != NULL) . . . 

In Java, you use a combination of the instanceof operator and a cast.

 if (staff[1] instanceof Manager) {    Manager boss = (Manager)staff[1];    . . . } 

Abstract Classes

As you move up the inheritance hierarchy, classes become more general and probably more abstract. At some point, the ancestor class becomes so general that you think of it more as a basis for other classes than as a class with specific instances you want to use. Consider, for example, an extension of our Employee class hierarchy. An employee is a person, and so is a student. Let us extend our class hierarchy to include classes Person and Student. Figure 5-2 shows the inheritance relationships between these classes.

Figure 5-2. Inheritance diagram for Person and its subclasses

graphics/05fig02.gif

Why bother with so high a level of abstraction? There are some attributes that make sense for every person, such as the name. Both students and employees have names, and introducing a common superclass lets us factor out the getName method to a higher level in the inheritance hierarchy.

Now let's add another method, getDescription, whose purpose is to return a brief description of the person, such as:

 an employee with a salary of $50,000.00 a student majoring in computer science 

It is easy to implement this method for the Employee and Student classes. But what information can you provide in the Person class? The Person class knows nothing about the person except for the name. Of course, you could implement Person.getDescription() to return an empty string. But there is a better way. If you use the abstract keyword, you do not need to implement the method at all.

 public abstract String getDescription();    // no implementation required 

For added clarity, a class with one or more abstract methods must itself be declared abstract.

 abstract class Person {  . . .    public abstract String getDescription(); } 

In addition to abstract methods, abstract classes can have concrete data and methods. For example, the Person class stores the name of the person and has a concrete method that returns it.

 abstract class Person {    public Person(String n)    {       name = n;    }    public abstract String getDescription();    public String getName()    {        return name;    }    private String name; } 

graphics/exclamatory_icon.gif

Many programmers think that abstract classes should have only abstract methods. However, this is not true. It always makes sense to move as much functionality as possible into a superclass, whether or not it is abstract. In particular, move common fields and nonabstract methods to the abstract superclass.

Abstract methods act as placeholder methods that are implemented in the subclasses. When you extend an abstract class, you have two choices. You can leave some or all of the abstract methods undefined. Then you must tag the subclass as abstract as well. Or you can define all methods. Then the subclass is no longer abstract.

For example, we will define a Student class that extends the abstract Person class and implements the getDescription method. Because none of the methods of the Student class are abstract, it does not need to be declared as an abstract class.

A class can even be declared as abstract even though it has no abstract methods.

Abstract classes cannot be instantiated. That is, if a class is declared as abstract, no objects of that class can be created. For example, the expression

 new Person("Vince Vu") 

is an error. However, you can create objects of concrete subclasses.

Note that you can still create object variables of an abstract class, but such a variable must refer to an object of a nonabstract subclass. For example,

 Person p = new Student("Vince Vu", "Economics"); 

Here p is a variable of the abstract type Person that refers to an instance of the nonabstract subclass Student.

graphics/cplus_icon.gif

In C++, an abstract method is called a pure virtual function and is tagged with a trailing = 0 , such as in

 class Person // C++ { public:    virtual string getDescription() = 0;    . . . }; 

A C++ class is abstract if it has at least one pure virtual function. In C++, there is no special keyword to denote abstract classes.

Let us define a concrete subclass Student that extends the abstract Person class:

 class Student extends Person {    public Student(String n, String m)    {       super(n);       major = m;    }    public String getDescription()    {       return "a student majoring in " + major;    }    private String major; } 

The Student class defines the getDescription method. Therefore, all methods in the Student class are concrete, and the class is no longer an abstract class.

The program shown in Example 5-2 defines the abstract superclass Person and two concrete subclasses Employee and Student. We fill an array of Person references with employee and student objects.

 Person[] people = new Person[2]; people[0] = new Employee(. . .); people[1] = new Student(. . .); 

We then print the names and descriptions of these objects:

 for (int i = 0; i < people.length; i++) {    Person p = people[i];    System.out.println(p.getName() + ", " + p.getDescription()); } 

Some people are baffled by the call:

 p.getDescription() 

Isn't this a call an undefined method? Keep in mind that the variable p never refers to a Person object since it is impossible to construct a Person object. The variable p always refers to an object of a concrete subclass such as Employee or Student. For these objects, the getDescription method is defined.

Could you have omitted the abstract method altogether from the Person superclass and simply defined the getDescription methods in the Employee and Student subclasses? Then you wouldn't have been able to invoke the getDescription method on the variable p. The compiler ensures that you only invoke methods that are declared in the class.

Abstract methods are an important concept in the Java programming language. You will encounter them most commonly inside interfaces. For more information about interfaces, please turn to Chapter 6.

Example 5-2 PersonTest.java
  1. import java.text.*;  2.  3. public class PersonTest  4. {  5.    public static void main(String[] args)  6.    {  7.       Person[] people = new Person[2];  8.  9.       // fill the people array with Student and Employee objects 10.       people[0] 11.          = new Employee("Harry Hacker", 50000); 12.       people[1] 13.          = new Student("Maria Morris", "computer science"); 14. 15.       // print out names and descriptions of all Person objects 16.       for (int i = 0; i < people.length; i++) 17.       { 18.          Person p = people[i]; 19.          System.out.println(p.getName() + ", " 20.             + p.getDescription()); 21.       } 22.    } 23. } 24. 25. abstract class Person 26. { 27.    public Person(String n) 28.    { 29.       name = n; 30.    } 31. 32.    public abstract String getDescription(); 33. 34.    public String getName() 35.    { 36.       return name; 37.    } 38. 39.    private String name; 40. } 41. 42. class Employee extends Person 43. { 44.    public Employee(String n, double s) 45.    { 46.       // pass name to superclass constructor 47.       super(n); 48.       salary = s; 49.    } 50. 51.    public double getSalary() 52.    { 53.       return salary; 54.    } 55. 56.    public String getDescription() 57.    { 58.       NumberFormat formatter 59.          = NumberFormat.getCurrencyInstance(); 60.       return "an employee with a salary of " 61.          + formatter.format(salary); 62.    } 63. 64.    public void raiseSalary(double byPercent) 65.    { 66.       double raise = salary * byPercent / 100; 67.       salary += raise; 68.    } 69. 70.    private double salary; 71. } 72. 73. class Student extends Person 74. { 75.    /** 76.       @param n the student's name 77.       @param m the student's major 78.    */ 79.    public Student(String n, String m) 80.    { 81.       // pass n to superclass constructor 82.       super(n); 83.       major = m; 84.    } 85. 86.    public String getDescription() 87.    { 88.       return "a student majoring in " + major; 89.    } 90. 91.    private String major; 92.} 

Protected Access

As you know, fields in a class are best tagged as private, and methods are usually tagged as public. Any features declared private won't be visible to other classes. As we said at the beginning of this chapter, this is also true for subclasses: a subclass cannot access the private fields of its superclass.

There are times, however, when you want to restrict a method to subclasses only, or, less commonly, to allow subclass methods to access a superclass field. In that case, you declare a class feature as protected. For example, if the superclass Employee declares the hireDay field as protected instead of private, then the Manager methods can access it directly.

However, the Manager class methods can only peek inside the hireDay field of Manager objects, not of other Employee objects. This restriction is made so that you can't abuse the protected mechanism and form subclasses just to gain access to the protected fields.

In practice, use the protected attribute with caution. Suppose your class is used by other programmers and you designed it with protected fields. Unbeknownst to you, other programmers may inherit classes from your class and then start accessing your protected fields. In this case, you can no longer change the implementation of your class without upsetting the other programmers. That is against the spirit of OOP, which encourages data encapsulation.

Protected methods make more sense. A class may declare a method as protected if it is tricky to use. This indicates that the subclasses (which, presumably, know their ancestors well) can be trusted to use the method correctly, but other classes cannot.

A good example of this kind of method is the clone method of the Object class see Chapter 6 for more details.

graphics/cplus_icon.gif

As it happens, protected features in Java are visible to all subclasses as well as all other classes in the same package. This is slightly different from the C++ meaning of protected, and it makes the notion of protected in Java even less safe than in C++.

Here is a summary of the four access modifiers in Java that control visibility:

  1. Visible to the class only (private).

  2. Visible to the world (public).

  3. Visible to the package and all subclasses (protected).

  4. Visible to the package the (unfortunate) default. No modifiers are needed.


       
    Top
     



    Core Java 2(c) Volume I - Fundamentals
    Building on Your AIX Investment: Moving Forward with IBM eServer pSeries in an On Demand World (MaxFacts Guidebook series)
    ISBN: 193164408X
    EAN: 2147483647
    Year: 2003
    Pages: 110
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net