Chapter 5. Inheritance

Chapter 4 introduced you to classes and objects. In this chapter, you will learn about inheritance, another fundamental concept of object-oriented programming. The idea behind inheritance is that you can create new classes that are built on existing classes. When you inherit from an existing class, you reuse (or inherit) its methods, and you can add new methods and fields to adapt your new class to new situations. This technique is essential in Java programming.

This chapter also covers reflection, the ability to find out more about classes and their properties in a running program. Reflection is a powerful feature, but it is undeniably complex. Since reflection is of greater interest to tool builders than to application programmers, you can probably glance over that part of the chapter upon first reading and come back to it later.

5.1 Classes, Superclasses, and Subclasses

Let’s return to the Employee class that we discussed in the previous chapter. Suppose (alas) you work for a company where managers are treated differently from 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.


Image Note

In this chapter, we use the classic example of employees and managers, but we must ask you to take this example with a grain of salt. In the real world, an employee can become a manager, so you would want to model being a manager as a role of an employee, not a subclass. In our example, however, we assume the corporate world is populated by two kinds of people: those who are forever employees, and those who have always been managers.


5.1.1 Defining Subclasses

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

public class Manager extends Employee
{
   added methods and fields
}


Image C++ Note

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.


Image Note

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 thus is said to be a superset of the set of managers. Or, to 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:

public class Manager extends Employee
{
   private double bonus;
   ...
   public void setBonus(double bonus)
   {
      this.bonus = 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 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 taken 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 in the superclass and more specialized methods in its subclasses. Factoring out common functionality by moving it to a superclass is common in object-oriented programming.

5.1.2 Overriding Methods

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:

public 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. Recall that only the Employee methods have direct access to the private fields of the Employee class. This means that the getSalary method of the Manager class cannot directly access the salary field. 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 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 chain of calls to the same method, leading 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;
}


Image Note

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 methods or override the methods of the superclass. However, inheritance can never take away any fields or methods.


Image C++ Note

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.


5.1.3 Subclass Constructors

To complete our example, let us supply a constructor.

public Manager(String name, double salary, int year, int month, int day)
{
   super(name, salary, 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, the no-argument constructor of the superclass is invoked. If the superclass does not have a no-argument constructor and the subclass constructor does not call another superclass constructor explicitly, the Java compiler reports an error.


Image Note

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 constructor parameters are either passed to another constructor of the same class (this) or a constructor of the superclass (super).



Image C++ Note

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 name, double salary, int year, int month, int day) // C++
: Employee(name, salary, year, month, day)
{
   bonus = 0;
}


After you redefine 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 (Employee e : staff)
   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 whose 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 or Manager.

When e refers to an Employee object, 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 discuss both topics in more detail in this chapter.


Image C++ Note

In C++, you need to declare a member function as virtual if you want dynamic binding. In Java, 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.)


Listing 5.1 contains a program that shows how the salary computation differs for Employee (Listing 5.2) and Manager (Listing 5.3) objects.

Listing 5.1 inheritance/ManagerTest.java


 1   package inheritance;
 2
 3   /**
 4    * This program demonstrates inheritance.
 5    * @version 1.21 2004-02-21
 6    * @author Cay Horstmann
 7    */
 8   public class ManagerTest
 9   {
10      public static void main(String[] args)
11      {
12         // construct a Manager object
13         Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
14         boss.setBonus(5000);
15
16         Employee[] staff = new Employee[3];
17
18         // fill the staff array with Manager and Employee objects
19
20         staff[0] = boss;
21         staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
22         staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
23
24         // print out information about all Employee objects
25         for (Employee e : staff)
26            System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
27      }
28   }


Listing 5.2 inheritance/Employee.java


 1   package inheritance;
 2
 3   import java.time.*;
 4
 5   public class Employee
 6   {
 7      private String name;
 8      private double salary;
 9      private LocalDate hireDay;
10
11      public Employee(String name, double salary, int year, int month, int day)
12      {
13         this.name = name;
14         this.salary = salary;
15         hireDay = LocalDate.of(year, month, day);
16      }
17
18      public String getName()
19      {
20         return name;
21      }
22
23      public double getSalary()
24      {
25         return salary;
26      }
27
28      public LocalDate getHireDay()
29      {
30         return hireDay;
31      }
32
33      public void raiseSalary(double byPercent)
34      {
35         double raise = salary * byPercent / 100;
36         salary += raise;
37      }
38   }


Listing 5.3 inheritance/Manager.java


 1   package inheritance;
 2
 3   public class Manager extends Employee
 4   {
 5      private double bonus;
 6
 7      /**
 8       * @param name the employee's name
 9       * @param salary the salary
10       * @param year the hire year
11       * @param month the hire month
12       * @param day the hire day
13       */
14      public Manager(String name, double salary, int year, int month, int day)
15      {
16         super(name, salary, year, month, day);
17         bonus = 0;
18      }
19
20      public double getSalary()
21      {
22         double baseSalary = super.getSalary();
23         return baseSalary + bonus;
24      }
25
26      public void setBonus(double b)
27      {
28         bonus = b;
29      }
30   }


5.1.4 Inheritance Hierarchies

Inheritance need not stop at deriving one layer of classes. We could have an Executive class that extends Manager, for example. The collection of all classes extending 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.

Image

Figure 5.1 Employee inheritance hierarchy

There is usually more than one chain of descent from a distant ancestor class. You could form subclasses Programmer or Secretary that extend 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.


Image C++ Note

In C++, a class can have multiple superclasses. Java does not support multiple inheritance. For ways to recover much of the functionality of multiple inheritance, see Section 6.1, “Interfaces,” on p. 288.


5.1.5 Polymorphism

A simple rule can help you decide 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 to an object of any subclass of the Employee class (such as Manager, Executive, Secretary, and so on).

We took advantage of this principle in Listing 5.1:

Manager boss = new Manager(. . .);
Employee[] staff = new Employee[3];
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.


Image Caution

In Java, arrays of subclass references can be converted to arrays of superclass references without a cast. For example, consider this array of managers:

Manager[] managers = new Manager[10];

It is legal to convert this array to an Employee[] array:

Employee[] staff = managers; // OK

Sure, why not, you may think. After all, if managers[i] is a Manager, it is also an Employee. But actually, something surprising is going on. Keep in mind that managers and staff are references to the same array. Now consider the statement

staff[0] = new Employee("Harry Hacker", ...);

The compiler will cheerfully allow this assignment. But staff[0] and managers[0] are the same reference, so it looks as if we managed to smuggle a mere employee into the management ranks. That would be very bad—calling managers[0].setBonus(1000) would try to access a nonexistent instance field and would corrupt neighboring memory.

To make sure no such corruption can occur, all arrays remember the element type with which they were created, and they monitor that only compatible references are stored into them. For example, the array created as new Manager[10] remembers that it is an array of managers. Attempting to store an Employee reference causes an ArrayStoreException.


5.1.6 Understanding Method Calls

It is important to understand exactly how a method call is applied to an object. Let’s say we call x.f(args), and the implicit parameter x is declared to be an object of class C. Here is what happens:

1. The compiler looks at the declared type of the object and the method name. 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 accessible methods called f in the superclasses of C. (Private methods of the superclass are not accessible.)

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

2. Next, the compiler determines the types of the arguments 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 arguments, 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 multiple methods all match after applying conversions, the compiler reports an error.

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


Image Note

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, you override the superclass method.

The return type is not part of the signature. However, when you override a method, you need to keep the return type compatible. A subclass may change the return type to a subtype of the original type. For example, suppose the Employee class has a method

public Employee getBuddy() { ... }

A manager would never want to have a lowly employee as a buddy. To reflect that fact, the Manager subclass can override this method as

public Manager getBuddy() { ... } // OK to change return type

We say that the two getBuddy methods have covariant return types.


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 runtime. 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, 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 for each class a method table 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(param), 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 Listing 5.1. The declared type of e is Employee. The Employee class has a single method, called getSalary, with no method parameters. Therefore, in this case, we don’t worry about overloading resolution.

The getSalary method is not private, static, or final, so 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 the whole story—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 runtime, 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 modifying 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.


Image Caution

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, the subclass method must also be declared public. It is a common error to accidentally omit the public specifier for the subclass method. The compiler then complains that you try to supply a more restrictive access privilege.


5.1.7 Preventing Inheritance: Final Classes and Methods

Occasionally, you want to prevent someone from forming a subclass 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, suppose we want to prevent others from subclassing the Executive class. Simply declare the class using the final modifier, as follows:

public 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:

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


Image Note

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 final, only the methods, not the fields, are automatically final.


There is only one good reason to make a method or class final: to make sure its semantics cannot be changed in a subclass. For example, the getTime and setTime methods of the Calendar class are final. This indicates that the designers of the Calendar class have taken over responsibility for the conversion between the Date class and the calendar state. No subclass should be allowed to mess up this arrangement. Similarly, the String class is a final class. That means nobody can define a subclass of String. In other words, if you have a String reference, you know it refers to a String and nothing but a String.

Some programmers believe that you should declare all methods as final unless you have a good reason to want polymorphism. In fact, in C++ and C#, methods do not use polymorphism unless you specifically request it. That may be a bit extreme, but we agree that it is a good idea to think carefully about final methods and classes when you design a class hierarchy.

In the early days of Java, some programmers used the final keyword hoping to avoid the overhead of dynamic binding. If a method is not overridden, and it is short, then a compiler can optimize the method call away—a process called inlining. For example, inlining the call e.getName() replaces it with the field access e.name. This is a worthwhile improvement—CPUs hate branching because it interferes with their strategy of prefetching instructions while processing the current one. However, if getName can be overridden in another class, then the compiler cannot inline it because it has no way of knowing what the overriding code may do.

Fortunately, the just-in-time compiler in the virtual machine can do a better job than a traditional compiler. It knows exactly which classes extend a given class, and it can check whether any class actually overrides a given method. If a method is short, frequently called, and not actually overridden, the just-in-time compiler can inline the method. What happens if the virtual machine loads another subclass that overrides an inlined method? Then the optimizer must undo the inlining. That takes time, but it happens rarely.

5.1.8 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 may need to convert an object reference from one class to another. To actually make a cast of an object reference, 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 ManagerTest class, the staff array had to be an array of Employee objects because some of its elements 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 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).

The compiler checks that you do not promise too much when you store a value in a variable. If you assign a subclass reference to a superclass variable, you are promising less, and the compiler will simply let you do it. If you assign a superclass reference to a subclass variable, you are promising more. Then you must use a cast so that your promise can be checked at runtime.

What happens if you try to cast down an inheritance chain and 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 a ClassCastException. 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

String c = (String) staff[1];

is a compile-time error because String 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.


Image Note

The test

x instanceof C

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


Actually, converting the type of an object by 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 objects of both classes. 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 for some reason you 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 uncaught ClassCastException to terminate your program. In general, it is best to minimize the use of casts and the instanceof operator.


Image C++ Note

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 need to use a combination of the instanceof operator and a cast.

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


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

Image

Figure 5.2 Inheritance diagram for Person and its subclasses

Why bother with so high a level of abstraction? There are some attributes that make sense for every person, such as 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 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.

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

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

public abstract class Person
{
   private String name;

   public Person(String name)
   {
      this.name = name;
   }

   public abstract String getDescription();

   public String getName()
   {
       return name;
   }
}


Image Tip

Some programmers don’t realize that abstract classes can have concrete methods. You should always move common fields and methods (whether abstract or not) to the superclass (whether abstract or not).


Abstract methods act as placeholders for 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, and 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. None of the methods of the Student class are abstract, so it does not need to be declared as an abstract class.

A class can even be declared as abstract 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.


Image C++ Note

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 class Person:

public class Student extends Person
{
   private String major;

   public Student(String name, String major)
   {
      super(name);
      this.major = major;
   }

   public String getDescription()
   {
      return "a student majoring in " + 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 Listing 5.4 defines the abstract superclass Person (Listing 5.5) and two concrete subclasses, Employee (Listing 5.6) and Student (Listing 5.7). 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 (Person p : people)
   System.out.println(p.getName() + ", " + p.getDescription());

Some people are baffled by the call

p.getDescription()

Isn’t this a call to an undefined method? Keep in mind that the variable p never refers to a Person object because it is impossible to construct an object of the abstract Person class. 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, simply defining the getDescription methods in the Employee and Student subclasses? If you did that, you wouldn’t have been able to invoke the getDescription method on the variable p. The compiler ensures that you invoke only 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, turn to Chapter 6.

Listing 5.4 abstractClasses/PersonTest.java


 1   package abstractClasses;
 2
 3   /**
 4    * This program demonstrates abstract classes.
 5    * @version 1.01 2004-02-21
 6    * @author Cay Horstmann
 7    */
 8   public class PersonTest
 9   {
10      public static void main(String[] args)
11      {
12         Person[] people = new Person[2];
13
14         // fill the people array with Student and Employee objects
15         people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
16         people[1] = new Student("Maria Morris", "computer science");
17
18         // print out names and descriptions of all Person objects
19         for (Person p : people)
20            System.out.println(p.getName() + ", " + p.getDescription());
21      }
22   }


Listing 5.5 abstractClasses/Person.java


 1   package abstractClasses;
 2
 3   public abstract class Person
 4   {
 5      public abstract String getDescription();
 6      private String name;
 7
 8      public Person(String name)
 9      {
10         this.name = name;
11      }
12
13      public String getName()
14      {
15         return name;
16      }
17   }


Listing 5.6 abstractClasses/Employee.java


 1   package abstractClasses;
 2
 3   import java.time.*;
 4
 5   public class Employee extends Person
 6   {
 7      private double salary;
 8      private LocalDate hireDay;
 9
10      public Employee(String name, double salary, int year, int month, int day)
11      {
12         super(name);
13         this.salary = salary;
14         hireDay = LocalDate.of(year, month, day);
15      }
16
17      public double getSalary()
18      {
19         return salary;
20      }
21
22      public LocalDate getHireDay()
23      {
24         return hireDay;
25      }
26
27      public String getDescription()
28      {
29         return String.format("an employee with a salary of $%.2f", salary);
30      }
31
32      public void raiseSalary(double byPercent)
33      {
34         double raise = salary * byPercent / 100;
35         salary += raise;
36      }
37   }


Listing 5.7 abstractClasses/Student.java


 1   package abstractClasses;
 2
 3   public class Student extends Person
 4   {
 5      private String major;
 6
 7      /**
 8       * @param nama the student's name
 9       * @param major the student's major
10       */
11      public Student(String name, String major)
12      {
13         // pass n to superclass constructor
14         super(name);
15         this.major = major;
16      }
17
18      public String getDescription()
19      {
20         return "a student majoring in " + major;
21      }
22   }


5.1.10 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 peek inside the hireDay field of Manager objects only, not of other Employee objects. This restriction is made so that you can’t abuse the protected mechanism by forming subclasses just to gain access to the protected fields.

In practice, use protected fields with caution. Suppose your class is used by other programmers and you designed it with protected fields. Unknown to you, other programmers may inherit classes from your class and start accessing your protected fields. In this case, you can no longer change the implementation of your class without upsetting those 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 ancestor 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.


Image C++ Note

As it happens, protected features in Java are visible to all subclasses as well as to 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.

5.2 Object: The Cosmic Superclass

The Object class is the ultimate ancestor—every class in Java extends Object. However, you never have to write

public class Employee extends Object

The ultimate superclass Object is taken for granted if no superclass is explicitly mentioned. Since every class in Java extends Object, it is important to be familiar with the services provided by the Object class. We go over the basic ones in this chapter; consult the later chapters or view the online documentation for what is not covered here. (Several methods of Object come up only when dealing with concurrency—see Chapter 14 for more on threads.)

You can use a variable of type Object to refer to objects of any type:

Object obj = new Employee("Harry Hacker", 35000);

Of course, a variable of type Object is only useful as a generic holder for arbitrary values. To do anything specific with the value, you need to have some knowledge about the original type and apply a cast:

Employee e = (Employee) obj;

In Java, only the values of primitive types (numbers, characters, and boolean values) are not objects.

All array types, no matter whether they are arrays of objects or arrays of primitive types, are class types that extend the Object class.

Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK


Image C++ Note

In C++, there is no cosmic root class. However, every pointer can be converted to a void* pointer.


5.2.1 The equals Method

The equals method in the Object class tests whether one object is considered equal to another. The equals method, as implemented in the Object class, determines whether two object references are identical. This is a pretty reasonable default—if two objects are identical, they should certainly be equal. For quite a few classes, nothing else is required. For example, it makes little sense to compare two PrintStream objects for equality. However, you will often want to implement state-based equality testing, in which two objects are considered equal when they have the same state.

For example, let us consider two employees equal if they have the same name, salary, and hire date. (In an actual employee database, it would be more sensible to compare IDs instead. We use this example to demonstrate the mechanics of implementing the equals method.)

public class Employee
{
   ...
   public boolean equals(Object otherObject)
   {
      // a quick test to see if the objects are identical
      if (this == otherObject) return true;

      // must return false if the explicit parameter is null
      if (otherObject == null) return false;

      // if the classes don't match, they can't be equal
      if (getClass() != otherObject.getClass())
         return false;

      // now we know otherObject is a non-null Employee
      Employee other = (Employee) otherObject;

      // test whether the fields have identical values
      return name.equals(other.name)
         && salary == other.salary
         && hireDay.equals(other.hireDay);
   }
}

The getClass method returns the class of an object—we discuss this method in detail later in this chapter. In our test, two objects can only be equal when they belong to the same class.


Image Tip

To guard against the possibility that name or hireDay are null, use the Objects.equals method. The call Objects.equals(a, b) returns true if both arguments are null, false if only one is null, and calls a.equals(b) otherwise. With that method, the last statement of the Employee.equals method becomes

return Objects.equals(name, other.name)
   && salary == other.salary
   && Objects.equals(hireDay, other.hireDay);


When you define the equals method for a subclass, first call equals on the superclass. If that test doesn’t pass, then the objects can’t be equal. If the superclass fields are equal, you are ready to compare the instance fields of the subclass.

public class Manager extends Employee
{
   ...
   public boolean equals(Object otherObject)
   {
      if (!super.equals(otherObject)) return false;
      // super.equals checked that this and otherObject belong to the same class
      Manager other = (Manager) otherObject;
      return bonus == other.bonus;
   }
}

5.2.2 Equality Testing and Inheritance

How should the equals method behave if the implicit and explicit parameters don’t belong to the same class? This has been an area of some controversy. In the preceding example, the equals method returns false if the classes don’t match exactly. But many programmers use an instanceof test instead:

if (!(otherObject instanceof Employee)) return false;

This leaves open the possibility that otherObject can belong to a subclass. However, this approach can get you into trouble. Here is why. The Java Language Specification requires that the equals method has the following properties:

1. It is reflexive: For any non-null reference x, x.equals(x) should return true.

2. It is symmetric: For any references x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

3. It is transitive: For any references x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.

4. It is consistent: If the objects to which x and y refer haven’t changed, then repeated calls to x.equals(y) return the same value.

5. For any non-null reference x, x.equals(null) should return false.

These rules are certainly reasonable. You wouldn’t want a library implementor to ponder whether to call x.equals(y) or y.equals(x) when locating an element in a data structure.

However, the symmetry rule has subtle consequences when the parameters belong to different classes. Consider a call

e.equals(m)

where e is an Employee object and m is a Manager object, both of which happen to have the same name, salary, and hire date. If Employee.equals uses an instanceof test, the call returns true. But that means that the reverse call

m.equals(e)

also needs to return true—the symmetry rule does not allow it to return false or to throw an exception.

That leaves the Manager class in a bind. Its equals method must be willing to compare itself to any Employee, without taking manager-specific information into account! All of a sudden, the instanceof test looks less attractive.

Some authors have gone on record that the getClass test is wrong because it violates the substitution principle. A commonly cited example is the equals method in the AbstractSet class that tests whether two sets have the same elements. The AbstractSet class has two concrete subclasses, TreeSet and HashSet, that use different algorithms for locating set elements. You really want to be able to compare any two sets, no matter how they are implemented.

However, the set example is rather specialized. It would make sense to declare AbstractSet.equals as final, because nobody should redefine the semantics of set equality. (The method is not actually final. This allows a subclass to implement a more efficient algorithm for the equality test.)

The way we see it, there are two distinct scenarios:

• If subclasses can have their own notion of equality, then the symmetry requirement forces you to use the getClass test.

• If the notion of equality is fixed in the superclass, then you can use the instanceof test and allow objects of different subclasses to be equal to one another.

In the example with employees and managers, we consider two objects to be equal when they have matching fields. If we have two Manager objects with the same name, salary, and hire date, but with different bonuses, we want them to be different. Therefore, we used the getClass test.

But suppose we used an employee ID for equality testing. This notion of equality makes sense for all subclasses. Then we could use the instanceof test, and we should have declared Employee.equals as final.


Image Note

The standard Java library contains over 150 implementations of equals methods, with a mishmash of using instanceof, calling getClass, catching a ClassCastException, or doing nothing at all. Check out the API documentation of the java.sql.Timestamp class, where the implementors note with some embarrassment that they have painted themselves in a corner. The Timestamp class inherits from java.util.Date, whose equals method uses an instanceof test, and it is impossible to override equals to be both symmetric and accurate.


Here is a recipe for writing the perfect equals method:

1. Name the explicit parameter otherObject—later, you will need to cast it to another variable that you should call other.

2. Test whether this happens to be identical to otherObject:

if (this == otherObject) return true;

This statement is just an optimization. In practice, this is a common case. It is much cheaper to check for identity than to compare the fields.

3. Test whether otherObject is null and return false if it is. This test is required.

if (otherObject == null) return false;

4. Compare the classes of this and otherObject. If the semantics of equals can change in subclasses, use the getClass test:

if (getClass() != otherObject.getClass()) return false;

If the same semantics holds for all subclasses, you can use an instanceof test:

if (!(otherObject instanceof ClassName)) return false;

5. Cast otherObject to a variable of your class type:

ClassName other = (ClassName) otherObject

6. Now compare the fields, as required by your notion of equality. Use == for primitive type fields, Objects.equals for object fields. Return true if all fields match, false otherwise.

return field1 == other.field1
   && Objects.equals(field2, other.field2)
   && . . .;

If you redefine equals in a subclass, include a call to super.equals(other).


Image Tip

If you have fields of array type, you can use the static Arrays.equals method to check that the corresponding array elements are equal.



Image Caution

Here is a common mistake when implementing the equals method. Can you spot the problem?

public class Employee
{
   public boolean equals(Employee other)
   {
      return other != null
         && getClass() == other.getClass()
         && Objects.equals(name, other.name)
         && salary == other.salary
         && Objects.equals(hireDay, other.hireDay);
   }
   ...
}

This method declares the explicit parameter type as Employee. As a result, it does not override the equals method of the Object class but defines a completely unrelated method.

You can protect yourself against this type of error by tagging methods that are intended to override superclass methods with @Override:

@Override public boolean equals(Object other)

If you made a mistake and are defining a new method, the compiler reports an error. For example, suppose you add the following declaration to the Employee class:

@Override public boolean equals(Employee other)

An error is reported because this method doesn’t override any method from the Object superclass.


5.2.3 The hashCode Method

A hash code is an integer that is derived from an object. Hash codes should be scrambled—if x and y are two distinct objects, there should be a high probability that x.hashCode() and y.hashCode() are different. Table 5.1 lists a few examples of hash codes that result from the hashCode method of the String class.

Image

Table 5.1 Hash Codes Resulting from the hashCode Method

The String class uses the following algorithm to compute the hash code:

int hash = 0;
for (int i = 0; i < length(); i++)
   hash = 31 * hash + charAt(i);

The hashCode method is defined in the Object class. Therefore, every object has a default hash code. That hash code is derived from the object’s memory address. Consider this example:

String s = "Ok";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode() + " " + sb.hashCode());
String t = new String("Ok");
StringBuilder tb = new StringBuilder(t);
System.out.println(t.hashCode() + " " + tb.hashCode());

Table 5.2 shows the result.

Image

Table 5.2 Hash Codes of Strings and String Builders

Note that the strings s and t have the same hash code because, for strings, the hash codes are derived from their contents. The string builders sb and tb have different hash codes because no hashCode method has been defined for the StringBuilder class and the default hashCode method in the Object class derives the hash code from the object’s memory address.

If you redefine the equals method, you will also need to redefine the hashCode method for objects that users might insert into a hash table. (We discuss hash tables in Chapter 9.)

The hashCode method should return an integer (which can be negative). Just combine the hash codes of the instance fields so that the hash codes for different objects are likely to be widely scattered.

For example, here is a hashCode method for the Employee class:

public class Employee
{
   public int hashCode()
   {
      return 7 * name.hashCode()
         + 11 * new Double(salary).hashCode()
         + 13 * hireDay.hashCode();
   }
   ...
}

However, you can do better. First, use the null-safe method Objects.hashCode. It returns 0 if its argument is null and the result of calling hashCode on the argument otherwise. Also, use the static Double.hashCode method to avoid creating a Double object:

public int hashCode()
{
   return 7 * Objects.hashCode(name)
      + 11 * Double.hashCode(salary)
      + 13 * Objects.hashCode(hireDay);
}

Even better, when you need to combine multiple hash values, call Objects.hash with all of them. It will call Objects.hashCode for each argument and combine the values. Then the Employee.hashCode method is simply

public int hashCode()
{
   return Objects.hash(name, salary, hireDay);
}

Your definitions of equals and hashCode must be compatible: If x.equals(y) is true, then x.hashCode() must return the same value as y.hashCode(). For example, if you define Employee.equals to compare employee IDs, then the hashCode method needs to hash the IDs, not employee names or memory addresses.


Image Tip

If you have fields of an array type, you can use the static Arrays.hashCode method to compute a hash code composed of the hash codes of the array elements.


5.2.4 The toString Method

Another important method in Object is the toString method that returns a string representing the value of this object. Here is a typical example. The toString method of the Point class returns a string like this:

java.awt.Point[x=10,y=20]

Most (but not all) toString methods follow this format: the name of the class, then the field values enclosed in square brackets. Here is an implementation of the toString method for the Employee class:

public String toString()
{
   return "Employee[name=" + name
      + ",salary=" + salary
      + ",hireDay=" + hireDay
      + "]";
}

Actually, you can do a little better. Instead of hardwiring the class name into the toString method, call getClass().getName() to obtain a string with the class name.

public String toString()
{
   return getClass().getName()
      + "[name=" + name
      + ",salary=" + salary
      + ",hireDay=" + hireDay
      + "]";
}

Such toString method will also work for subclasses.

Of course, the subclass programmer should define its own toString method and add the subclass fields. If the superclass uses getClass().getName(), then the subclass can simply call super.toString(). For example, here is a toString method for the Manager class:

public class Manager extends Employee
{
   ...
   public String toString()
   {
      return super.toString()
        + "[bonus=" + bonus
        + "]";
   }
}

Now a Manager object is printed as

Manager[name=...,salary=...,hireDay=...][bonus=...]

The toString method is ubiquitous for an important reason: Whenever an object is concatenated with a string by the “+” operator, the Java compiler automatically invokes the toString method to obtain a string representation of the object. For example:

Point p = new Point(10, 20);
String message = "The current position is " + p;
   // automatically invokes p.toString()


Image Tip

Instead of writing x.toString(), you can write "" + x. This statement concatenates the empty string with the string representation of x that is exactly x.toString(). Unlike toString, this statement even works if x is of primitive type.


If x is any object and you call

System.out.println(x);

then the println method simply calls x.toString() and prints the resulting string.

The Object class defines the toString method to print the class name and the hash code of the object. For example, the call

System.out.println(System.out)

produces an output that looks like this:

java.io.PrintStream@2f6684

The reason is that the implementor of the PrintStream class didn’t bother to override the toString method.


Image Caution

Annoyingly, arrays inherit the toString method from Object, with the added twist that the array type is printed in an archaic format. For example,

int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 };
String s = "" + luckyNumbers;

yields the string "[I@1a46e30". (The prefix [I denotes an array of integers.) The remedy is to call the static Arrays.toString method instead. The code

String s = Arrays.toString(luckyNumbers);

yields the string "[2, 3, 5, 7, 11, 13]".

To correctly print multidimensional arrays (that is, arrays of arrays), use Arrays.deepToString.


The toString method is a great tool for logging. Many classes in the standard class library define the toString method so that you can get useful information about the state of an object. This is particularly useful in logging messages like this:

System.out.println("Current position = " + position);

As we explain in Chapter 7, an even better solution is to use an object of the Logger class and call

Logger.global.info("Current position = " + position);


Image Tip

We strongly recommend that you add a toString method to each class that you write. You, as well as other programmers who use your classes, will be grateful for the logging support.


The program in Listing 5.8 implements the equals, hashCode, and toString methods for the classes Employee (Listing 5.9) and Manager (Listing 5.10).

Listing 5.8 equals/EqualsTest.java


 1   package equals;
 2
 3   /**
 4    * This program demonstrates the equals method.
 5    * @version 1.12 2012-01-26
 6    * @author Cay Horstmann
 7    */
 8   public class EqualsTest
 9   {
10      public static void main(String[] args)
11      {
12         Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
13         Employee alice2 = alice1;
14         Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15);
15         Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1);
16
17         System.out.println("alice1 == alice2: " + (alice1 == alice2));
18
19         System.out.println("alice1 == alice3: " + (alice1 == alice3));
20
21         System.out.println("alice1.equals(alice3): " + alice1.equals(alice3));
22
23         System.out.println("alice1.equals(bob): " + alice1.equals(bob));
24
25         System.out.println("bob.toString(): " + bob);
26
27         Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);
28         Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
29         boss.setBonus(5000);
30         System.out.println("boss.toString(): " + boss);
31         System.out.println("carl.equals(boss): " + carl.equals(boss));
32         System.out.println("alice1.hashCode(): " + alice1.hashCode());
33         System.out.println("alice3.hashCode(): " + alice3.hashCode());
34         System.out.println("bob.hashCode(): " + bob.hashCode());
35         System.out.println("carl.hashCode(): " + carl.hashCode());
36      }
37   }


Listing 5.9 equals/Employee.java


 1   package equals;
 2
 3   import java.time.*;
 4   import java.util.Objects;
 5
 6   public class Employee
 7   {
 8      private String name;
 9      private double salary;
10      private LocalDate hireDay;
11
12      public Employee(String name, double salary, int year, int month, int day)
13      {
14         this.name = name;
15         this.salary = salary;
16         hireDay = LocalDate.of(year, month, day);
17      }
18
19      public String getName()
20      {
21         return name;
22      }
23
24      public double getSalary()
25      {
26         return salary;
27      }
28
29      public LocalDate getHireDay()
30      {
31         return hireDay;
32      }
33
34      public void raiseSalary(double byPercent)
35      {
36         double raise = salary * byPercent / 100;
37         salary += raise;
38      }
39
40      public boolean equals(Object otherObject)
41      {
42         // a quick test to see if the objects are identical
43         if (this == otherObject) return true;
44
45         // must return false if the explicit parameter is null
46         if (otherObject == null) return false;
47
48         // if the classes don't match, they can't be equal
49         if (getClass() != otherObject.getClass()) return false;
50
51         // now we know otherObject is a non-null Employee
52         Employee other = (Employee) otherObject;
53
54         // test whether the fields have identical values
55         return Objects.equals(name, other.name) && salary == other.salary
56               && Objects.equals(hireDay, other.hireDay);
57      }
58
59      public int hashCode()
60      {
61         return Objects.hash(name, salary, hireDay);
62      }
63
64      public String toString()
65      {
66         return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay
67               + "]";
68      }
69   }


Listing 5.10 equals/Manager.java


 1   package equals;
 2
 3   public class Manager extends Employee
 4   {
 5      private double bonus;
 6
 7      public Manager(String name, double salary, int year, int month, int day)
 8      {
 9         super(name, salary, year, month, day);
10         bonus = 0;
11      }
12
13      public double getSalary()
14      {
15         double baseSalary = super.getSalary();
16         return baseSalary + bonus;
17      }
18
19      public void setBonus(double bonus)
20      {
21         this.bonus = bonus;
22      }
23
24      public boolean equals(Object otherObject)
25      {
26         if (!super.equals(otherObject)) return false;
27         Manager other = (Manager) otherObject;
28         // super.equals checked that this and other belong to the same class
29         return bonus == other.bonus;
30      }
31
32      public int hashCode()
33      {
34         return java.util.Objects.hash(super.hashCode(), bonus)
35      }
36
37      public String toString()
38      {
39         return super.toString() + "[bonus=" + bonus + "]";
40      }
41   }


5.3 Generic Array Lists

In many programming languages—in particular, in C++—you have to fix the sizes of all arrays at compile time. Programmers hate this because it forces them into uncomfortable trade-offs. How many employees will be in a department? Surely no more than 100. What if there is a humongous department with 150 employees? Do we want to waste 90 entries for every department with just 10 employees?

In Java, the situation is much better. You can set the size of an array at runtime.

int actualSize = . . .;
Employee[] staff = new Employee[actualSize];

Of course, this code does not completely solve the problem of dynamically modifying arrays at runtime. Once you set the array size, you cannot change it easily. Instead, in Java you can deal with this common situation by using another Java class, called ArrayList. The ArrayList class is similar to an array, but it automatically adjusts its capacity as you add and remove elements, without any additional code.

ArrayList is a generic class with a type parameter. To specify the type of the element objects that the array list holds, you append a class name enclosed in angle brackets, such as ArrayList<Employee>. You will see in Chapter 8 how to define your own generic class, but you don’t need to know any of those technicalities to use the ArrayList type.

Here we declare and construct an array list that holds Employee objects:

ArrayList<Employee> staff = new ArrayList<Employee>();

It is a bit tedious that the type parameter Employee is used on both sides. As of Java SE 7, you can omit the type parameter on the right-hand side:

ArrayList<Employee> staff = new ArrayList<>();

This is called the “diamond” syntax because the empty brackets <> resemble a diamond. Use the diamond syntax together with the new operator. The compiler checks what happens to the new value. If it is assigned to a variable, passed into a method, or returned from a method, then the compiler checks the generic type of the variable, parameter, or method. It then places that type into the <>. In our example, the new ArrayList<>() is assigned to a variable of type ArrayList<Employee>. Therefore, the generic type is Employee.


Image Note

Before Java SE 5.0, there were no generic classes. Instead, there was a single ArrayList class, a one-size-fits-all collection that holds elements of type Object. You can still use ArrayList without a <...> suffix. It is considered a “raw” type, with the type parameter erased.



Image Note

In even older versions of Java, programmers used the Vector class for dynamic arrays. However, the ArrayList class is more efficient, and there is no longer any good reason to use the Vector class.


Use the add method to add new elements to an array list. For example, here is how you populate an array list with employee objects:

staff.add(new Employee("Harry Hacker", . . .));
staff.add(new Employee("Tony Tester", . . .));

The array list manages an internal array of object references. Eventually, that array will run out of space. This is where array lists work their magic: If you call add and the internal array is full, the array list automatically creates a bigger array and copies all the objects from the smaller to the bigger array.

If you already know, or have a good guess, how many elements you want to store, call the ensureCapacity method before filling the array list:

staff.ensureCapacity(100);

That call allocates an internal array of 100 objects. Then, the first 100 calls to add will not involve any costly reallocation.

You can also pass an initial capacity to the ArrayList constructor:

ArrayList<Employee> staff = new ArrayList<>(100);


Image Caution

Allocating an array list as

new ArrayList<>(100) // capacity is 100

is not the same as allocating a new array as

new Employee[100] // size is 100

There is an important distinction between the capacity of an array list and the size of an array. If you allocate an array with 100 entries, then the array has 100 slots, ready for use. An array list with a capacity of 100 elements has the potential of holding 100 elements (and, in fact, more than 100, at the cost of additional reallocations)—but at the beginning, even after its initial construction, an array list holds no elements at all.


The size method returns the actual number of elements in the array list. For example,

staff.size()

returns the current number of elements in the staff array list. This is the equivalent of

a.length

for an array a.

Once you are reasonably sure that the array list is at its permanent size, you can call the trimToSize method. This method adjusts the size of the memory block to use exactly as much storage space as is required to hold the current number of elements. The garbage collector will reclaim any excess memory.

Once you trim the size of an array list, adding new elements will move the block again, which takes time. You should only use trimToSize when you are sure you won’t add any more elements to the array list.


Image C++ Note

The ArrayList class is similar to the C++ vector template. Both ArrayList and vector are generic types. But the C++ vector template overloads the [] operator for convenient element access. Java does not have operator overloading, so it must use explicit method calls instead. Moreover, C++ vectors are copied by value. If a and b are two vectors, then the assignment a = b makes a into a new vector with the same length as b, and all elements are copied from b to a. The same assignment in Java makes both a and b refer to the same array list.


5.3.1 Accessing Array List Elements

Unfortunately, nothing comes for free. The automatic growth convenience that array lists give requires a more complicated syntax for accessing the elements. The reason is that the ArrayList class is not a part of the Java programming language; it is just a utility class programmed by someone and supplied in the standard library.

Instead of the pleasant [] syntax to access or change the element of an array, you use the get and set methods.

For example, to set the ith element, you use

staff.set(i, harry);

This is equivalent to

a[i] = harry;

for an array a. (As with arrays, the index values are zero based.)


Image Caution

Do not call list.set(i, x) until the size of the array list is larger than i. For example, the following code is wrong:

ArrayList<Employee> list = new ArrayList<>(100); // capacity 100, size 0
list.set(0, x); // no element 0 yet

Use the add method instead of set to fill up an array, and use set only to replace a previously added element.


To get an array list element, use

Employee e = staff.get(i);

This is equivalent to

Employee e = a[i];


Image Note

When there were no generic classes, the get method of the raw ArrayList class had no choice but to return an Object. Consequently, callers of get had to cast the returned value to the desired type:

Employee e = (Employee) staff.get(i);

The raw ArrayList is also a bit dangerous. Its add and set methods accept objects of any type. A call

staff.set(i, "Harry Hacker");

compiles without so much as a warning, and you run into grief only when you retrieve the object and try to cast it. If you use an ArrayList<Employee> instead, the compiler will detect this error.


You can sometimes get the best of both worlds—flexible growth and convenient element access—with the following trick. First, make an array list and add all the elements:

ArrayList<X> list = new ArrayList<>();
while (. . .)
{
   x = . . .;
   list.add(x);
}

When you are done, use the toArray method to copy the elements into an array:

X[] a = new X[list.size()];
list.toArray(a);

Sometimes, you need to add elements in the middle of an array list. Use the add method with an index parameter:

int n = staff.size() / 2;
staff.add(n, e);

The elements at locations n and above are shifted up to make room for the new entry. If the new size of the array list after the insertion exceeds the capacity, the array list reallocates its storage array.

Similarly, you can remove an element from the middle of an array list:

Employee e = staff.remove(n);

The elements located above it are copied down, and the size of the array is reduced by one.

Inserting and removing elements is not terribly efficient. It is probably not worth worrying about for small array lists. But if you store many elements and frequently insert and remove in the middle of a collection, consider using a linked list instead. We explain how to program with linked lists in Chapter 9.

You can use the “for each” loop to traverse the contents of an array list:

for (Employee e : staff)
   do something with e

This loop has the same effect as

for (int i = 0; i < staff.size(); i++)
{
   Employee e = staff.get(i);
   do something with e
}

Listing 5.11 is a modification of the EmployeeTest program of Chapter 4. The Employee[] array is replaced by an ArrayList<Employee>. Note the following changes:

• You don’t have to specify the array size.

• You use add to add as many elements as you like.

• You use size() instead of length to count the number of elements.

• You use a.get(i) instead of a[i] to access an element.

Listing 5.11 arrayList/ArrayListTest.java


 1   package arrayList;
 2
 3   import java.util.*;
 4
 5   /**
 6    * This program demonstrates the ArrayList class.
 7    * @version 1.11 2012-01-26
 8    * @author Cay Horstmann
 9    */
10   public class ArrayListTest
11   {
12      public static void main(String[] args)
13      {
14         // fill the staff array list with three Employee objects
15         ArrayList<Employee> staff = new ArrayList<>();
16
17         staff.add(new Employee("Carl Cracker", 75000, 1987, 12, 15));
18         staff.add(new Employee("Harry Hacker", 50000, 1989, 10, 1));
19         staff.add(new Employee("Tony Tester", 40000, 1990, 3, 15));
20
21         // raise everyone's salary by 5%
22         for (Employee e : staff)
23            e.raiseSalary(5);
24
25         // print out information about all Employee objects
26         for (Employee e : staff)
27            System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay="
28                  + e.getHireDay());
29      }
30   }


5.3.2 Compatibility between Typed and Raw Array Lists

In your own code, you will always want to use type parameters for added safety. In this section, you will see how to interoperate with legacy code that does not use type parameters.

Suppose you have the following legacy class:

public class EmployeeDB
{
   public void update(ArrayList list) { ... }
   public ArrayList find(String query) { ... }
}

You can pass a typed array list to the update method without any casts.

ArrayList<Employee> staff = ...;
employeeDB.update(staff);

The staff object is simply passed to the update method.


Image Caution

Even though you get no error or warning from the compiler, this call is not completely safe. The update method might add elements into the array list that are not of type Employee. When these elements are retrieved, an exception occurs. This sounds scary, but if you think about it, the behavior is simply as it was before generics were added to Java. The integrity of the virtual machine is never jeopardized. In this situation, you do not lose security, but you also do not benefit from the compile-time checks.


Conversely, when you assign a raw ArrayList to a typed one, you get a warning.

ArrayList<Employee> result = employeeDB.find(query); // yields warning


Image Note

To see the text of the warning, compile with the option -Xlint:unchecked.


Using a cast does not make the warning go away.

ArrayList<Employee> result = (ArrayList<Employee>) employeeDB.find(query);
   // yields another warning

Instead, you get a different warning, telling you that the cast is misleading.

This is the consequence of a somewhat unfortunate limitation of generic types in Java. For compatibility, the compiler translates all typed array lists into raw ArrayList objects after checking that the type rules were not violated. In a running program, all array lists are the same—there are no type parameters in the virtual machine. Thus, the casts (ArrayList) and (ArrayList<Employee>) carry out identical runtime checks.

There isn’t much you can do about that situation. When you interact with legacy code, study the compiler warnings and satisfy yourself that the warnings are not serious.

Once you are satisfied, you can tag the variable that receives the cast with the @SuppressWarnings("unchecked") annotation, like this:

@SuppressWarnings("unchecked") ArrayList<Employee> result =
   (ArrayList<Employee>) employeeDB.find(query); // yields another warning

5.4 Object Wrappers and Autoboxing

Occasionally, you need to convert a primitive type like int to an object. All primitive types have class counterparts. For example, a class Integer corresponds to the primitive type int. These kinds of classes are usually called wrappers. The wrapper classes have obvious names: Integer, Long, Float, Double, Short, Byte, Character, and Boolean. (The first six inherit from the common superclass Number.) The wrapper classes are immutable—you cannot change a wrapped value after the wrapper has been constructed. They are also final, so you cannot subclass them.

Suppose we want an array list of integers. Unfortunately, the type parameter inside the angle brackets cannot be a primitive type. It is not possible to form an ArrayList<int>. Here, the Integer wrapper class comes in. It is OK to declare an array list of Integer objects.

ArrayList<Integer> list = new ArrayList<>();


Image Caution

An ArrayList<Integer> is far less efficient than an int[] array because each value is separately wrapped inside an object. You would only want to use this construct for small collections when programmer convenience is more important than efficiency.


Fortunately, there is a useful feature that makes it easy to add an element of type int to an ArrayList<Integer>. The call

list.add(3);

is automatically translated to

list.add(Integer.valueOf(3));

This conversion is called autoboxing.


Image Note

You might think that autowrapping would be more consistent, but the “boxing” metaphor was taken from C#.


Conversely, when you assign an Integer object to an int value, it is automatically unboxed. That is, the compiler translates

int n = list.get(i);

into

int n = list.get(i).intValue();

Automatic boxing and unboxing even works with arithmetic expressions. For example, you can apply the increment operator to a wrapper reference:

Integer n = 3;
n++;

The compiler automatically inserts instructions to unbox the object, increment the resulting value, and box it back.

In most cases, you get the illusion that the primitive types and their wrappers are one and the same. There is just one point in which they differ considerably: identity. As you know, the == operator, applied to wrapper objects, only tests whether the objects have identical memory locations. The following comparison would therefore probably fail:

Integer a = 1000;
Integer b = 1000;
if (a == b) . . .

However, a Java implementation may, if it chooses, wrap commonly occurring values into identical objects, and thus the comparison might succeed. This ambiguity is not what you want. The remedy is to call the equals method when comparing wrapper objects.


Image Note

The autoboxing specification requires that boolean, byte, char <= 127, short, and int between -128 and 127 are wrapped into fixed objects. For example, if a and b had been initialized with 100 in the preceding example, then the comparison would have had to succeed.


There are a couple of other subtleties about autoboxing. First off, since wrapper class references can be null, it is possible for autounboxing to throw a NullPointerException:

Integer n = null;
System.out.println(2 * n); // Throws NullPointerException

Also, if you mix Integer and Double types in a conditional expression, then the Integer value is unboxed, promoted to double, and boxed into a Double:

Integer n = 1;
Double x = 2.0;
System.out.println(true ? n : x); // Prints 1.0

Finally, let us emphasize that boxing and unboxing is a courtesy of the compiler, not the virtual machine. The compiler inserts the necessary calls when it generates the bytecodes of a class. The virtual machine simply executes those bytecodes.

You will often see the number wrappers for another reason. The designers of Java found the wrappers a convenient place to put certain basic methods, such as those for converting strings of digits to numbers.

To convert a string to an integer, use the following statement:

int x = Integer.parseInt(s);

This has nothing to do with Integer objects—parseInt is a static method. But the Integer class was a good place to put it.

The API notes show some of the more important methods of the Integer class. The other number classes implement corresponding methods.


Image Caution

Some people think that the wrapper classes can be used to implement methods that can modify numeric parameters. However, that is not correct. Recall from Chapter 4 that it is impossible to write a Java method that increments an integer parameter because parameters to Java methods are always passed by value.

public static void triple(int x) // won't work
{
   x = 3 * x; // modifies local variable
}

Could we overcome this by using an Integer instead of an int?

public static void triple(Integer x) // won't work
{
   ...
}

The problem is that Integer objects are immutable: The information contained inside the wrapper can’t change. You cannot use these wrapper classes to create a method that modifies numeric parameters.

If you do want to write a method to change numeric parameters, you can use one of the holder types defined in the org.omg.CORBA package: IntHolder, BooleanHolder, and so on. Each holder type has a public (!) field value through which you can access the stored value.

public static void triple(IntHolder x)
{
   x.value = 3 * x.value;
}


5.5 Methods with a Variable Number of Parameters

It is possible to provide methods that can be called with a variable number of parameters. (These are sometimes called “varargs” methods.)

You have already seen such a method: printf. For example, the calls

System.out.printf("%d", n);

and

System.out.printf("%d %s", n, "widgets");

both call the same method, even though one call has two parameters and the other has three.

The printf method is defined like this:

public class PrintStream
{
   public PrintStream printf(String fmt, Object... args) { return format(fmt, args); }
}

Here, the ellipsis ... is part of the Java code. It denotes that the method can receive an arbitrary number of objects (in addition to the fmt parameter).

The printf method actually receives two parameters: the format string and an Object[] array that holds all other parameters. (If the caller supplies integers or other primitive type values, autoboxing turns them into objects.) It now faces the unenviable task of scanning the fmt string and matching up the ith format specifier with the value args[i].

In other words, for the implementor of printf, the Object... parameter type is exactly the same as Object[].

The compiler needs to transform each call to printf, bundling the parameters into an array and autoboxing as necessary:

System.out.printf("%d %s", new Object[] { new Integer(n), "widgets" } );

You can define your own methods with variable parameters, and you can specify any type for the parameters, even a primitive type. Here is a simple example: a function that computes the maximum of a variable number of values.

public static double max(double... values)
{
   double largest = Double.NEGATIVE_INFINITY;
   for (double v : values) if (v > largest) largest = v;
   return largest;
}

Simply call the function like this:

double m = max(3.1, 40.4, -5);

The compiler passes a new double[] { 3.1, 40.4, -5 } to the max function.


Image Note

It is legal to pass an array as the last parameter of a method with variable parameters. For example:

System.out.printf("%d %s", new Object[] { new Integer(1), "widgets" } );

Therefore, you can redefine an existing function whose last parameter is an array to a method with variable parameters, without breaking any existing code. For example, MessageFormat.format was enhanced in this way in Java SE 5.0. If you like, you can even declare the main method as

public static void main(String... args)


5.6 Enumeration Classes

You saw in Chapter 3 how to define enumerated types. Here is a typical example:

public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE }

The type defined by this declaration is actually a class. The class has exactly four instances—it is not possible to construct new objects.

Therefore, you never need to use equals for values of enumerated types. Simply use == to compare them.

You can, if you like, add constructors, methods, and fields to an enumerated type. Of course, the constructors are only invoked when the enumerated constants are constructed. Here is an example.

public enum Size
{
   SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");

   private String abbreviation;

   private Size(String abbreviation) { this.abbreviation = abbreviation; }
   public String getAbbreviation() { return abbreviation; }
}

All enumerated types are subclasses of the class Enum. They inherit a number of methods from that class. The most useful one is toString, which returns the name of the enumerated constant. For example, Size.SMALL.toString() returns the string "SMALL".

The converse of toString is the static valueOf method. For example, the statement

Size s = Enum.valueOf(Size.class, "SMALL");

sets s to Size.SMALL.

Each enumerated type has a static values method that returns an array of all values of the enumeration. For example, the call

Size[] values = Size.values();

returns the array with elements Size.SMALL, Size.MEDIUM, Size.LARGE, and Size.EXTRA_LARGE.

The ordinal method yields the position of an enumerated constant in the enum declaration, counting from zero. For example, Size.MEDIUM.ordinal() returns 1.

The short program in Listing 5.12 demonstrates how to work with enumerated types.


Image Note

The Enum class has a type parameter that we have ignored for simplicity. For example, the enumerated type Size actually extends Enum<Size>. The type parameter is used in the compareTo method. (We discuss the compareTo method in Chapter 6 and type parameters in Chapter 8.)


Listing 5.12 enums/EnumTest.java


 1   package enums;
 2
 3   import java.util.*;
 4
 5   /**
 6    * This program demonstrates enumerated types.
 7    * @version 1.0 2004-05-24
 8    * @author Cay Horstmann
 9    */
10   public class EnumTest
11   {
12      public static void main(String[] args)
13      {
14         Scanner in = new Scanner(System.in);
15         System.out.print("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) ");
16         String input = in.next().toUpperCase();
17         Size size = Enum.valueOf(Size.class, input);
18         System.out.println("size=" + size);
19         System.out.println("abbreviation=" + size.getAbbreviation());
20         if (size == Size.EXTRA_LARGE)
21            System.out.println("Good job--you paid attention to the _.");
22      }
23   }
24
25   enum Size
26   {
27      SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
28
29      private Size(String abbreviation) { this.abbreviation = abbreviation; }
30      public String getAbbreviation() { return abbreviation; }
31
32      private String abbreviation;
33   }


5.7 Reflection

The reflection library gives you a very rich and elaborate toolset to write programs that manipulate Java code dynamically. This feature is heavily used in JavaBeans, the component architecture for Java (see Volume II for more on JavaBeans). Using reflection, Java can support tools like those to which users of Visual Basic have grown accustomed. In particular, when new classes are added at design time or runtime, rapid application development tools can dynamically inquire about the capabilities of these classes.

A program that can analyze the capabilities of classes is called reflective. The reflection mechanism is extremely powerful. As the next sections show, you can use it to

• Analyze the capabilities of classes at runtime;

• Inspect objects at runtime—for example, to write a single toString method that works for all classes;

• Implement generic array manipulation code; and

• Take advantage of Method objects that work just like function pointers in languages such as C++.

Reflection is a powerful and complex mechanism; however, it is of interest mainly to tool builders, not application programmers. If you are interested in programming applications rather than tools for other Java programmers, you can safely skip the remainder of this chapter and return to it later.

5.7.1 The Class Class

While your program is running, the Java runtime system always maintains what is called runtime type identification on all objects. This information keeps track of the class to which each object belongs. Runtime type information is used by the virtual machine to select the correct methods to execute.

However, you can also access this information by working with a special Java class. The class that holds this information is called, somewhat confusingly, Class. The getClass() method in the Object class returns an instance of Class type.

Employee e;
...
Class cl = e.getClass();

Just like an Employee object describes the properties of a particular employee, a Class object describes the properties of a particular class. Probably the most commonly used method of Class is getName. This returns the name of the class. For example, the statement

System.out.println(e.getClass().getName() + " " + e.getName());

prints

Employee Harry Hacker

if e is an employee, or

Manager Harry Hacker

if e is a manager.

If the class is in a package, the package name is part of the class name:

Random generator = new Random();
Class cl = generator.getClass();
String name = cl.getName(); // name is set to "java.util.Random"

You can obtain a Class object corresponding to a class name by using the static forName method.

String className = "java.util.Random";
Class cl = Class.forName(className);

Use this method if the class name is stored in a string that varies at runtime. This works if className is the name of a class or interface. Otherwise, the forName method throws a checked exception. See Section 5.7.2, “A Primer on Catching Exceptions,” on p. 263 for how to supply an exception handler whenever you use this method.


Image Tip

At startup, the class containing your main method is loaded. It loads all classes that it needs. Each of those loaded classes loads the classes that it needs, and so on. That can take a long time for a big application, frustrating the user. You can give the users of your program an illusion of a faster start with the following trick. Make sure the class containing the main method does not explicitly refer to other classes. In it, display a splash screen. Then manually force the loading of other classes by calling Class.forName.


A third method for obtaining an object of type Class is a convenient shorthand. If T is any Java type (or the void keyword), then T.class is the matching class object. For example:

Class cl1 = Random.class; // if you import java.util.*;
Class cl2 = int.class;
Class cl3 = Double[].class;

Note that a Class object really describes a type, which may or may not be a class. For example, int is not a class, but int.class is nevertheless an object of type Class.


Image Note

The Class class is actually a generic class. For example, Employee.class is of type Class<Employee>. We are not dwelling on this issue because it would further complicate an already abstract concept. For most practical purposes, you can ignore the type parameter and work with the raw Class type. See Chapter 8 for more information on this issue.



Image Caution

For historical reasons, the getName method returns somewhat strange names for array types:

Double[].class.getName() returns "[Ljava.lang.Double;".

int[].class.getName() returns "[I".


The virtual machine manages a unique Class object for each type. Therefore, you can use the == operator to compare class objects. For example:

if (e.getClass() == Employee.class) . . .

Another example of a useful method is one that lets you create an instance of a class on the fly. This method is called, naturally enough, newInstance(). For example,

e.getClass().newInstance();

creates a new instance of the same class type as e. The newInstance method calls the no-argument constructor to initialize the newly created object. An exception is thrown if the class does not have a no-argument constructor.

A combination of forName and newInstance lets you create an object from a class name stored in a string.

String s = "java.util.Random";
Object m = Class.forName(s).newInstance();


Image Note

If you need to provide parameters for the constructor of a class you want to create by name in this manner, you can’t use the above statements. Instead, you must use the newInstance method in the Constructor class.



Image C++ Note

The newInstance method corresponds to the idiom of a virtual constructor in C++. However, virtual constructors in C++ are not a language feature but just an idiom that needs to be supported by a specialized library. The Class class is similar to the type_info class in C++, and the getClass method is equivalent to the typeid operator. The Java Class is quite a bit more versatile than type_info, though. The C++ type_info can only reveal a string with the name of the type, not create new objects of that type.


5.7.2 A Primer on Catching Exceptions

We cover exception handling fully in Chapter 7, but in the meantime you will occasionally encounter methods that threaten to throw exceptions.

When an error occurs at runtime, a program can “throw an exception.” Throwing an exception is more flexible than terminating the program because you can provide a handler that “catches” the exception and deals with it.

If you don’t provide a handler, the program still terminates and prints a message to the console, giving the type of the exception. You may have already seen exception reports when you accidentally used a null reference or overstepped the bounds of an array.

There are two kinds of exceptions: unchecked exceptions and checked exceptions. With checked exceptions, the compiler checks that you provide a handler. However, many common exceptions, such as accessing a null reference, are unchecked. The compiler does not check whether you provided a handler for these errors—after all, you should spend your mental energy on avoiding these mistakes rather than coding handlers for them.

But not all errors are avoidable. If an exception can occur despite your best efforts, then the compiler insists that you provide a handler. The Class.forName method is an example of a method that throws a checked exception. In Chapter 7, you will see several exception handling strategies. For now, we just show you the simplest handler implementation.

Place one or more statements that might throw checked exceptions inside a try block. Then provide the handler code in the catch clause.

try
{
   statements that might throw exceptions
}
catch (Exception e)
{
   handler action
}

Here is an example:

try
{
   String name = . . .; // get class name
   Class cl = Class.forName(name); // might throw exception
   do something with cl
}
catch (Exception e)
{
   e.printStackTrace();
}

If the class name doesn’t exist, the remainder of the code in the try block is skipped and the program enters the catch clause. (Here, we print a stack trace by using the printStackTrace method of the Throwable class. Throwable is the superclass of the Exception class.) If none of the methods in the try block throws an exception, the handler code in the catch clause is skipped.

You only need to supply an exception handler for checked exceptions. It is easy to find out which methods throw checked exceptions—the compiler will complain whenever you call a method that threatens to throw a checked exception and you don’t supply a handler.

5.7.3 Using Reflection to Analyze the Capabilities of Classes

Here is a brief overview of the most important parts of the reflection mechanism for letting you examine the structure of a class.

The three classes Field, Method, and Constructor in the java.lang.reflect package describe the fields, methods, and constructors of a class, respectively. All three classes have a method called getName that returns the name of the item. The Field class has a method getType that returns an object, again of type Class, that describes the field type. The Method and Constructor classes have methods to report the types of the parameters, and the Method class also reports the return type. All three of these classes also have a method called getModifiers that returns an integer, with various bits turned on and off, that describes the modifiers used, such as public and static. You can then use the static methods in the Modifier class in the java.lang.reflect package to analyze the integer that getModifiers returns. Use methods like isPublic, isPrivate, or isFinal in the Modifier class to tell whether a method or constructor was public, private, or final. All you have to do is have the appropriate method in the Modifier class work on the integer that getModifiers returns. You can also use the Modifier.toString method to print the modifiers.

The getFields, getMethods, and getConstructors methods of the Class class return arrays of the public fields, methods, and constructors that the class supports. This includes public members of superclasses. The getDeclaredFields, getDeclaredMethods, and getDeclaredConstructors methods of the Class class return arrays consisting of all fields, methods, and constructors that are declared in the class. This includes private, package, and protected members, but not members of superclasses.

Listing 5.13 shows you how to print out all information about a class. The program prompts you for the name of a class and writes out the signatures of all methods and constructors as well as the names of all instance fields of a class. For example, if you enter

java.lang.Double

the program prints

public class java.lang.Double extends java.lang.Number
{
   public java.lang.Double(java.lang.String);
   public java.lang.Double(double);

   public int hashCode();
   public int compareTo(java.lang.Object);
   public int compareTo(java.lang.Double);
   public boolean equals(java.lang.Object);
   public java.lang.String toString();
   public static java.lang.String toString(double);
   public static java.lang.Double valueOf(java.lang.String);
   public static boolean isNaN(double);
   public boolean isNaN();
   public static boolean isInfinite(double);
   public boolean isInfinite();
   public byte byteValue();
   public short shortValue();
   public int intValue();
   public long longValue();
   public float floatValue();
   public double doubleValue();
   public static double parseDouble(java.lang.String);
   public static native long doubleToLongBits(double);
   public static native long doubleToRawLongBits(double);
   public static native double longBitsToDouble(long);

   public static final double POSITIVE_INFINITY;
   public static final double NEGATIVE_INFINITY;
   public static final double NaN;
   public static final double MAX_VALUE;
   public static final double MIN_VALUE;
   public static final java.lang.Class TYPE;
   private double value;
   private static final long serialVersionUID;
}

What is remarkable about this program is that it can analyze any class that the Java interpreter can load, not just the classes that were available when the program was compiled. We will use this program in the next chapter to peek inside the inner classes that the Java compiler generates automatically.

Listing 5.13 reflection/ReflectionTest.java


 1   package reflection;
 2
 3   import java.util.*;
 4   import java.lang.reflect.*;
 5
 6   /**
 7    * This program uses reflection to print all features of a class.
 8    * @version 1.1 2004-02-21
 9    * @author Cay Horstmann
10    */
11   public class ReflectionTest
12   {
13      public static void main(String[] args)
14      {
15         // read class name from command line args or user input
16         String name;
17         if (args.length > 0) name = args[0];
18         else
19         {
20            Scanner in = new Scanner(System.in);
21            System.out.println("Enter class name (e.g. java.util.Date): ");
22            name = in.next();
23         }
24
25         try
26         {
27            // print class name and superclass name (if != Object)
28            Class cl = Class.forName(name);
29            Class supercl = cl.getSuperclass();
30            String modifiers = Modifier.toString(cl.getModifiers());
31            if (modifiers.length() > 0) System.out.print(modifiers + " ");
32            System.out.print("class " + name);
33            if (supercl != null && supercl != Object.class) System.out.print(" extends "
34                  + supercl.getName());
35
36            System.out.print(" { ");
37            printConstructors(cl);
38            System.out.println();
39            printMethods(cl);
40            System.out.println();
41            printFields(cl);
42            System.out.println("}");
43         }
44         catch (ClassNotFoundException e)
45         {
46            e.printStackTrace();
47         }
48         System.exit(0);
49     }
50
51     /**
52      * Prints all constructors of a class
53      * @param cl a class
54      */
55     public static void printConstructors(Class cl)
56     {
57        Constructor[] constructors = cl.getDeclaredConstructors();
58
59        for (Constructor c : constructors)
60        {
61           String name = c.getName();
62           System.out.print(" ");
63           String modifiers = Modifier.toString(c.getModifiers());
64           if (modifiers.length() > 0) System.out.print(modifiers + " ");
65           System.out.print(name + "(");
66
67           // print parameter types
68           Class[] paramTypes = c.getParameterTypes();
69           for (int j = 0; j < paramTypes.length; j++)
70           {
71              if (j > 0) System.out.print(", ");
72              System.out.print(paramTypes[j].getName());
73           }
74           System.out.println(");");
75        }
76     }
77
78     /**
79      * Prints all methods of a class
80      * @param cl a class
81      */
82     public static void printMethods(Class cl)
83     {
84        Method[] methods = cl.getDeclaredMethods();
85
86        for (Method m : methods)
87        {
88           Class retType = m.getReturnType();
89           String name = m.getName();
90
91           System.out.print(" ");
92           // print modifiers, return type and method name
93           String modifiers = Modifier.toString(m.getModifiers());
94           if (modifiers.length() > 0) System.out.print(modifiers + " ");
95           System.out.print(retType.getName() + " " + name + "(");
96
97           // print parameter types
98           Class[] paramTypes = m.getParameterTypes();
99           for (int j = 0; j < paramTypes.length; j++)
100          {
101             if (j > 0) System.out.print(", ");
102             System.out.print(paramTypes[j].getName());
103          }
104          System.out.println(");");
105       }
106    }
107
108    /**
109     * Prints all fields of a class
110     * @param cl a class
111     */
112    public static void printFields(Class cl)
113    {
114       Field[] fields = cl.getDeclaredFields();
115
116       for (Field f : fields)
117       {
118          Class type = f.getType();
119          String name = f.getName();
120          System.out.print(" ");
121          String modifiers = Modifier.toString(f.getModifiers());
122          if (modifiers.length() > 0) System.out.print(modifiers + " ");
123          System.out.println(type.getName() + " " + name + ";");
124       }
125    }
126 }


5.7.4 Using Reflection to Analyze Objects at Runtime

In the preceding section, we saw how we can find out the names and types of the data fields of any object:

• Get the corresponding Class object.

• Call getDeclaredFields on the Class object.

In this section, we will go one step further and actually look at the contents of the fields. Of course, it is easy to look at the contents of a specific field of an object whose name and type are known when you write a program. But reflection lets you look at fields of objects that were not known at compile time.

The key method to achieve this is the get method in the Field class. If f is an object of type Field (for example, one obtained from getDeclaredFields) and obj is an object of the class of which f is a field, then f.get(obj) returns an object whose value is the current value of the field of obj. This is all a bit abstract, so let’s run through an example.

Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
Class cl = harry.getClass();
   // the class object representing Employee
Field f = cl.getDeclaredField("name");
   // the name field of the Employee class
Object v = f.get(harry);
   // the value of the name field of the harry object, i.e., the String object "Harry Hacker"

Actually, there is a problem with this code. Since the name field is a private field, the get method will throw an IllegalAccessException. You can only use get to get the values of accessible fields. The security mechanism of Java lets you find out what fields an object has, but it won’t let you read the values of those fields unless you have access permission.

The default behavior of the reflection mechanism is to respect Java access control. However, if a Java program is not controlled by a security manager that disallows it, you can override access control. To do this, invoke the setAccessible method on a Field, Method, or Constructor object. For example:

f.setAccessible(true); // now OK to call f.get(harry);

The setAccessible method is a method of the AccessibleObject class, the common superclass of the Field, Method, and Constructor classes. This feature is provided for debuggers, persistent storage, and similar mechanisms. We use it for a generic toString method later in this section.

There is another issue with the get method that we need to deal with. The name field is a String, and so it is not a problem to return the value as an Object. But suppose we want to look at the salary field. That is a double, and in Java, number types are not objects. To handle this, you can either use the getDouble method of the Field class, or you can call get, whereby the reflection mechanism automatically wraps the field value into the appropriate wrapper class—in this case, Double.

Of course, you can also set the values that you can get. The call f.set(obj, value) sets the field represented by f of the object obj to the new value.

Listing 5.14 shows how to write a generic toString method that works for any class. It uses getDeclaredFields to obtain all data fields. It then uses the setAccessible convenience method to make all fields accessible. For each field, it obtains the name and the value. Each value is turned into a string by recursively invoking toString.

The generic toString method needs to address a couple of complexities. Cycles of references could cause an infinite recursion. Therefore, the ObjectAnalyzer keeps track of objects that were already visited. Also, to peek inside arrays, you need a different approach. You’ll learn about the details in the next section.

You can use this toString method to peek inside any object. For example, the call

ArrayList<Integer> squares = new ArrayList<>();
for (int i = 1; i <= 5; i++) squares.add(i * i);
System.out.println(new ObjectAnalyzer().toString(squares));

yields the printout

java.util.ArrayList[elementData=class java.lang.Object[]{java.lang.Integer[value=1][][],
java.lang.Integer[value=4][][],java.lang.Integer[value=9][][],java.lang.Integer[value=16][][],
java.lang.Integer[value=25][][],null,null,null,null,null},size=5][modCount=5][][]

You can use this generic toString method to implement the toString methods of your own classes, like this:

public String toString()
{
   return new ObjectAnalyzer().toString(this);
}

This is a hassle-free method for supplying a toString method that you may find useful in your own programs.

Listing 5.14 objectAnalyzer/ObjectAnalyzerTest.java


 1   package objectAnalyzer;
 2
 3   import java.util.ArrayList;
 4
 5   /**
 6    * This program uses reflection to spy on objects.
 7    * @version 1.12 2012-01-26
 8    * @author Cay Horstmann
 9    */
10   public class ObjectAnalyzerTest
11   {
12      public static void main(String[] args)
13      {
14         ArrayList<Integer> squares = new ArrayList<>();
15         for (int i = 1; i <= 5; i++)
16            squares.add(i * i);
17         System.out.println(new ObjectAnalyzer().toString(squares));
18      }
19   }


Listing 5.15 objectAnalyzer/ObjectAnalyzer.java


 1   package objectAnalyzer;
 2
 3   import java.lang.reflect.AccessibleObject;
 4   import java.lang.reflect.Array;
 5   import java.lang.reflect.Field;
 6   import java.lang.reflect.Modifier;
 7   import java.util.ArrayList;
 8
 9   public class ObjectAnalyzer
10   {
11      private ArrayList<Object> visited = new ArrayList<>();
12
13      /**
14       * Converts an object to a string representation that lists all fields.
15       * @param obj an object
16       * @return a string with the object's class name and all field names and
17       * values
18       */
19      public String toString(Object obj)
20      {
21         if (obj == null) return "null";
22         if (visited.contains(obj)) return "...";
23         visited.add(obj);
24         Class cl = obj.getClass();
25         if (cl == String.class) return (String) obj;
26         if (cl.isArray())
27         {
28            String r = cl.getComponentType() + "[]{";
29            for (int i = 0; i < Array.getLength(obj); i++)
30            {
31               if (i > 0) r += ",";
32               Object val = Array.get(obj, i);
33               if (cl.getComponentType().isPrimitive()) r += val;
34               else r += toString(val);
35            }
36            return r + "}";
37         }
38
39         String r = cl.getName();
40         // inspect the fields of this class and all superclasses
41         do
42         {
43            r += "[";
44            Field[] fields = cl.getDeclaredFields();
45            AccessibleObject.setAccessible(fields, true);
46            // get the names and values of all fields
47            for (Field f : fields)
48            {
49               if (!Modifier.isStatic(f.getModifiers()))
50               {
51                  if (!r.endsWith("[")) r += ",";
52                  r += f.getName() + "=";
53                  try
54                  {
55                     Class t = f.getType();
56                     Object val = f.get(obj);
57                     if (t.isPrimitive()) r += val;
58                     else r += toString(val);
59                  }
60                  catch (Exception e)
61                  {
62                     e.printStackTrace();
63                  }
64               }
65            }
66            r += "]";
67            cl = cl.getSuperclass();
68          }
69          while (cl != null);
70
71          return r;
72       }
73   }


5.7.5 Using Reflection to Write Generic Array Code

The Array class in the java.lang.reflect package allows you to create arrays dynamically. This is used, for example, in the implementation of the copyOf method in the Arrays class. Recall how this method can be used to grow an array that has become full.

Employee[] a = new Employee[100];
 ...
// array is full
a = Arrays.copyOf(a, 2 * a.length);

How can one write such a generic method? It helps that an Employee[] array can be converted to an Object[] array. That sounds promising. Here is a first attempt:

public static Object[] badCopyOf(Object[] a, int newLength) // not useful
{
   Object[] newArray = new Object[newLength];
   System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
   return newArray;
}

However, there is a problem with actually using the resulting array. The type of array that this code returns is an array of objects (Object[]) because we created the array using the line of code

new Object[newLength]

An array of objects cannot be cast to an array of employees (Employee[]). The virtual machine would generate a ClassCastException at runtime. The point is that, as we mentioned earlier, a Java array remembers the type of its entries—that is, the element type used in the new expression that created it. It is legal to cast an Employee[] temporarily to an Object[] array and then cast it back, but an array that started its life as an Object[] array can never be cast into an Employee[] array. To write this kind of generic array code, we need to be able to make a new array of the same type as the original array. For this, we need the methods of the Array class in the java.lang.reflect package. The key is the static newInstance method of the Array class that constructs a new array. You must supply the type for the entries and the desired length as parameters to this method.

Object newArray = Array.newInstance(componentType, newLength);

To actually carry this out, we need to get the length and the component type of the new array.

We obtain the length by calling Array.getLength(a). The static getLength method of the Array class returns the length of an array. To get the component type of the new array:

1. First, get the class object of a.

2. Confirm that it is indeed an array.

3. Use the getComponentType method of the Class class (which is defined only for class objects that represent arrays) to find the right type for the array.

Why is getLength a method of Array but getComponentType a method of Class? We don’t know—the distribution of the reflection methods seems a bit ad hoc at times.

Here’s the code:

public static Object goodCopyOf(Object a, int newLength)
{
   Class cl = a.getClass();
   if (!cl.isArray()) return null;
   Class componentType = cl.getComponentType();
   int length = Array.getLength(a);
   Object newArray = Array.newInstance(componentType, newLength);
   System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
   return newArray;
}

Note that this copyOf method can be used to grow arrays of any type, not just arrays of objects.

int[] a = { 1, 2, 3, 4, 5 };
a = (int[]) goodCopyOf(a, 10);

To make this possible, the parameter of goodCopyOf is declared to be of type Object, not an array of objects (Object[]). The integer array type int[] can be converted to an Object, but not to an array of objects!

Listing 5.16 shows both methods in action. Note that the cast of the return value of badcopyOf will throw an exception.

Listing 5.16 arrays/CopyOfTest.java


 1   package arrays;
 2
 3   import java.lang.reflect.*;
 4   import java.util.*;
 5
 6   /**
 7    * This program demonstrates the use of reflection for manipulating arrays.
 8    * @version 1.2 2012-05-04
 9    * @author Cay Horstmann
10    */
11   public class CopyOfTest
12   {
13      public static void main(String[] args)
14      {
15         int[] a = { 1, 2, 3 };
16         a = (int[]) goodCopyOf(a, 10);
17         System.out.println(Arrays.toString(a));
18
19         String[] b = { "Tom", "Dick", "Harry" };
20         b = (String[]) goodCopyOf(b, 10);
21         System.out.println(Arrays.toString(b));
22
23         System.out.println("The following call will generate an exception.");
24         b = (String[]) badCopyOf(b, 10);
25      }
26
27      /**
28       * This method attempts to grow an array by allocating a new array and copying all elements.
29       * @param a the array to grow
30       * @param newLength the new length
31       * @return a larger array that contains all elements of a. However, the returned array has
32       * type Object[], not the same type as a
33       */
34      public static Object[] badCopyOf(Object[] a, int newLength) // not useful
35      {
36         Object[] newArray = new Object[newLength];
37         System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
38         return newArray;
39      }
40
41      /**
42       * This method grows an array by allocating a new array of the same type and
43       * copying all elements.
44       * @param a the array to grow. This can be an object array or a primitive
45       * type array
46       * @return a larger array that contains all elements of a.
47       */
48      public static Object goodCopyOf(Object a, int newLength)
49      {
50         Class cl = a.getClass();
51         if (!cl.isArray()) return null;
52         Class componentType = cl.getComponentType();
53         int length = Array.getLength(a);
54         Object newArray = Array.newInstance(componentType, newLength);
55         System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
56         return newArray;
57      }
58   }


5.7.6 Invoking Arbitrary Methods

In C and C++, you can execute an arbitrary function through a function pointer. On the surface, Java does not have method pointers—that is, ways of giving the location of a method to another method, so that the second method can invoke it later. In fact, the designers of Java have said that method pointers are dangerous and error-prone, and that Java interfaces (discussed in the next chapter) are a superior solution. However, the reflection mechanism allows you to call arbitrary methods.


Image Note

Among the nonstandard language extensions that Microsoft added to its Java derivatives, J++ and C#, is another method pointer type, called a delegate, that is different from the Method class that we discuss in this section. However, inner classes (which we will introduce in the next chapter) are a more useful construct than delegates.


Recall that you can inspect a field of an object with the get method of the Field class. Similarly, the Method class has an invoke method that lets you call the method that is wrapped in the current Method object. The signature for the invoke method is

Object invoke(Object obj, Object... args)

The first parameter is the implicit parameter, and the remaining objects provide the explicit parameters.

For a static method, the first parameter is ignored—you can set it to null.

For example, if m1 represents the getName method of the Employee class, the following code shows how you can call it:

String n = (String) m1.invoke(harry);

If the return type is a primitive type, the invoke method will return the wrapper type instead. For example, suppose that m2 represents the getSalary method of the Employee class. Then, the returned object is actually a Double, and you must cast it accordingly. Use automatic unboxing to turn it into a double:

double s = (Double) m2.invoke(harry);

How do you obtain a Method object? You can, of course, call getDeclaredMethods and search through the returned array of Method objects until you find the method you want. Or, you can call the getMethod method of the Class class. This is similar to the getField method that takes a string with the field name and returns a Field object. However, there may be several methods with the same name, so you need to be careful that you get the right one. For that reason, you must also supply the parameter types of the desired method. The signature of getMethod is

Method getMethod(String name, Class... parameterTypes)

For example, here is how you can get method pointers to the getName and raiseSalary methods of the Employee class:

Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary", double.class);

Now that you have seen the rules for using Method objects, let’s put them to work. Listing 5.17 is a program that prints a table of values for a mathematical function such as Math.sqrt or Math.sin. The printout looks like this:

public static native double java.lang.Math.sqrt(double)
      1.0000 |      1.0000
      2.0000 |      1.4142
      3.0000 |      1.7321
      4.0000 |      2.0000
      5.0000 |      2.2361
      6.0000 |      2.4495
      7.0000 |      2.6458
      8.0000 |      2.8284
      9.0000 |      3.0000
     10.0000 |      3.1623

The code for printing a table is, of course, independent of the actual function that is being tabulated.

double dx = (to - from) / (n - 1);
for (double x = from; x <= to; x += dx)
{
   double y = (Double) f.invoke(null, x);
   System.out.printf("%10.4f | %10.4f%n", x, y);
}

Here, f is an object of type Method. The first parameter of invoke is null because we are calling a static method.

To tabulate the Math.sqrt function, we set f to

Math.class.getMethod("sqrt", double.class)

That is the method of the Math class that has the name sqrt and a single parameter of type double.

Listing 5.17 shows the complete code of the generic tabulator and a couple of test runs.

As this example clearly shows, you can do anything with Method objects that you can do with function pointers in C (or delegates in C#). Just as in C, this style of programming is usually quite inconvenient, and always error-prone. What happens if you invoke a method with the wrong parameters? The invoke method throws an exception.

Also, the parameters and return values of invoke are necessarily of type Object. That means you must cast back and forth a lot. As a result, the compiler is deprived of the chance to check your code, so errors surface only during testing, when they are more tedious to find and fix. Moreover, code that uses reflection to get at method pointers is significantly slower than code that simply calls methods directly.

For that reason, we suggest that you use Method objects in your own programs only when absolutely necessary. Using interfaces and, as of Java SE 8, lambda expressions (the subject of the next chapter) is almost always a better idea. In particular, we echo the developers of Java and suggest not using Method objects for callback functions. Using interfaces for the callbacks leads to code that runs faster and is a lot more maintainable.

Listing 5.17 methods/MethodTableTest.java


 1   package methods;
 2
 3   import java.lang.reflect.*;
 4
 5   /**
 6    * This program shows how to invoke methods through reflection.
 7    * @version 1.2 2012-05-04
 8    * @author Cay Horstmann
 9    */
10   public class MethodTableTest
11   {
12      public static void main(String[] args) throws Exception
13      {
14         // get method pointers to the square and sqrt methods
15         Method square = MethodTableTest.class.getMethod("square", double.class);
16         Method sqrt = Math.class.getMethod("sqrt", double.class);
17
18         // print tables of x- and y-values
19
20         printTable(1, 10, 10, square);
21         printTable(1, 10, 10, sqrt);
22      }
23
24      /**
25       * Returns the square of a number
26       * @param x a number
27       * @return x squared
28       */
29      public static double square(double x)
30      {
31         return x * x;
32      }
33
34      /**
35       * Prints a table with x- and y-values for a method
36       * @param from the lower bound for the x-values
37       * @param to the upper bound for the x-values
38       * @param n the number of rows in the table
39       * @param f a method with a double parameter and double return value
40       */
41      public static void printTable(double from, double to, int n, Method f)
42      {
43         // print out the method as table header
44         System.out.println(f);
45
46         double dx = (to - from) / (n - 1);
47
48         for (double x = from; x <= to; x += dx)
49         {
50            try
51            {
52               double y = (Double) f.invoke(null, x);
53               System.out.printf("%10.4f | %10.4f%n", x, y);
54            }
55            catch (Exception e)
56            {
57               e.printStackTrace();
58            }
59         }
60      }
61   }


5.8 Design Hints for Inheritance

We want to end this chapter with some hints that we have found useful when using inheritance.

1. Place common operations and fields in the superclass.

This is why we put the name field into the Person class instead of replicating it in the Employee and Student classes.

2. Don’t use protected fields.

Some programmers think it is a good idea to define most instance fields as protected, “just in case,” so that subclasses can access these fields if they need to. However, the protected mechanism doesn’t give much protection, for two reasons. First, the set of subclasses is unbounded—anyone can form a subclass of your classes and then write code that directly accesses protected instance fields, thereby breaking encapsulation. And second, in the Java programming language, all classes in the same package have access to protected fields, whether or not they are subclasses.

However, protected methods can be useful to indicate methods that are not ready for general use and should be redefined in subclasses.

3. Use inheritance to model the “is–a” relationship.

Inheritance is a handy code-saver, but sometimes people overuse it. For example, suppose we need a Contractor class. Contractors have names and hire dates, but they do not have salaries. Instead, they are paid by the hour, and they do not stay around long enough to get a raise. There is the temptation to form a subclass Contractor from Employee and add an hourlyWage field.

public class Contractor extends Employee
{
   private double hourlyWage;
   ...
}

This is not a good idea, however, because now each contractor object has both a salary and hourly wage field. It will cause you no end of grief when you implement methods for printing paychecks or tax forms. You will end up writing more code than you would have written by not inheriting in the first place.

The contractor-employee relationship fails the “is–a” test. A contractor is not a special case of an employee.

4. Don’t use inheritance unless all inherited methods make sense.

Suppose we want to write a Holiday class. Surely every holiday is a day, and days can be expressed as instances of the GregorianCalendar class, so we can use inheritance.

class Holiday extends GregorianCalendar { . . . }

Unfortunately, the set of holidays is not closed under the inherited operations. One of the public methods of GregorianCalendar is add. And add can turn holidays into nonholidays:

Holiday christmas;
christmas.add(Calendar.DAY_OF_MONTH, 12);

Therefore, inheritance is not appropriate in this example.

Note that this problem does not arise if you extend LocalDate. Because that class is immutable, there is no method that could turn a holiday into a nonholiday.

5. Don’t change the expected behavior when you override a method.

The substitution principle applies not just to syntax but, more importantly, to behavior. When you override a method, you should not unreasonably change its behavior. The compiler can’t help you—it cannot check whether your redefinitions make sense. For example, you can “fix” the issue of the add method in the Holiday class by redefining add, perhaps to do nothing, or to throw an exception, or to move on to the next holiday.

However, such a fix violates the substitution principle. The sequence of statements

int d1 = x.get(Calendar.DAY_OF_MONTH);
x.add(Calendar.DAY_OF_MONTH, 1);
int d2 = x.get(Calendar.DAY_OF_MONTH);
System.out.println(d2 - d1);

should have the expected behavior, no matter whether x is of type GregorianCalendar or Holiday.

Of course, therein lies the rub. Reasonable and unreasonable people can argue at length about what the expected behavior is. For example, some authors argue that the substitution principle requires Manager.equals to ignore the bonus field because Employee.equals ignores it. These discussions are pointless if they occur in a vacuum. Ultimately, what matters is that you do not circumvent the intent of the original design when you override methods in subclasses.

6. Use polymorphism, not type information.

Whenever you find code of the form

if (x is of type 1)
   action1(x);
else if (x is of type 2)
   action2(x);

think polymorphism.

Do action1 and action2 represent a common concept? If so, make the concept a method of a common superclass or interface of both types. Then, you can simply call

x.action();

and have the dynamic dispatch mechanism inherent in polymorphism launch the correct action.

Code that uses polymorphic methods or interface implementations is much easier to maintain and extend than code using multiple type tests.

7. Don’t overuse reflection.

The reflection mechanism lets you write programs with amazing generality, by detecting fields and methods at runtime. This capability can be extremely useful for systems programming, but it is usually not appropriate in applications. Reflection is fragile—with it, the compiler cannot help you find programming errors. Any errors are found at runtime and result in exceptions.

You have now seen how Java supports the fundamentals of object-oriented programming: classes, inheritance, and polymorphism. In the next chapter, we will tackle two advanced topics that are very important for using Java effectively: interfaces and lambda expressions.

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

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