Chapter 7

Programming with Abstract Classes and Interfaces

In this lesson you learn about so-called abstract classes, and then you build a complete application that will illustrate how to design and implement programs with abstract classes and interfaces. You also learn about the notion of polymorphism.

Abstract Classes

If a class is declared abstract it can’t be instantiated. The keyword abstract has to be placed in the declaration of a class. The abstract class may have abstract method(s). The question is “Who needs a class that can’t be instantiated?”

It’s easiest to answer this question by showing you how to use abstract classes while designing an application. While previous lessons ended with assignments, this lesson will start with one.

Assignment

A company has employees and contractors. Design the classes without using interfaces to represent the people who work for this company. The classes should have the following methods:

changeAddress() 
promote() 
giveDayOff() 
increasePay() 

A one-time promotion means giving one day off and raising the salary by a specified percentage. The method increasePay() should raise the yearly salary for employees and increase the hourly rate for contractors.

Solution with an Abstract Class

Since increasePay() has to be implemented differently for Employee and Contractor, let’s declare it as abstract. The class Person will also have three concrete methods. The fact that the abstract class Person cannot be instantiated forces us, the developers, to implement abstract methods in subclasses.

Let’s start by redesigning the class Person from the Try It section of Lesson 6. That version of the class didn’t have the method increasePay(), which was a part of the Payable interface. As per the previous lesson’s assignment, add the following concrete methods: changeAddress(), giveDayOff(), and promote() (see Listing 7-1).

This is a different approach from the one in Lesson 6, which used interfaces — here some methods are implemented in the superclass and some are not. This solution won’t be using any interfaces.

download.eps

Listing 7-1: Abstract class Person

package com.practicaljava.lesson7;
 
public abstract public class Person {
 
       private String name;
 
       int INCREASE_CAP = 20;  // cap on pay increase 
       
       public Person(String name){
              this.name=name;
       }
 
       public String getName(){
              return "Person's name is " + name; 
       }
 
       public void changeAddress(String address){
                 System.out.println("New address is" + address);
       } 
 
       private void giveDayOff(){
              System.out.println("Giving a day off to " + name);
       }
 
       public void promote(int percent){
                 System.out.println(" Promoting a worker...");
                 giveDayOff();
 
                 //calling an abstract method
                 increasePay(percent); 
        } 
        
        // an abstract method to be implemented in subclasses
        public abstract boolean increasePay(int percent);
}

Please note that even though the method increasePay() is abstract, the designer of the class Person doesn’t have to know the specifics of raising pay, and subclasses may even be programmed by other developers. But he or she can write code that calls increasePay(), as in the method promote(). This is allowed because by the time the concrete class is instantiated, this method will definitely have been implemented. For simplicity, I didn’t write any code that looks like actual increasing pay — this is irrelevant for understanding the concept of abstract classes.

The next step is to create the subclasses Employee and Contractor, implementing the method increasePay() in two different ways, as shown in in Listings 7-2 and 7-3.

download.eps

Listing 7-2: Class Employee

package com.practicaljava.lesson7;
 
public class Employee extends Person{
 
       public Employee(String name){
              super(name);
       }
       public boolean increasePay(int percent) {
              System.out.println("Increasing salary by " + 
                     percent + "%. "+ getName()); 
              return true;
       }
}
download.eps

Listing 7-3: Class Contractor

package com.practicaljava.lesson7;
 
public class Contractor extends Person {
       
       public Contractor(String name){
              super(name);
       }
       public boolean increasePay(int percent) {
              if(percent < INCREASE_CAP){
                 System.out.println("Increasing hourly rate by " + 
                                     percent + "%. "+ getName()); 
                return true;
             } else {
                System.out.println("Sorry, can't increase hourly rate by more 
                       than " + INCREASE_CAP + "%. "+ getName());
                return false;
             }
        }
}

Programmers writing subclasses are forced to write an implementation of increasePay() according to its signature, declared in the abstract class. If they declare a method increasing pay that has a different name or argument list, their classes will remain abstract. So they don’t have a choice — they have to play by the rules dictated in the abstract class.

The class TestPayIncrease2 in Listing 7-4 shows how to use the classes Employee and Contractor for promoting workers.

download.eps

Listing 7-4: Class TestPayIncrease2

import com.practicaljava.lesson7.*;
 
public class TestPayIncrease2 {
 
      public static void main(String[] args) {
 
        Person workers[] = new Person[3];
              workers[0] = new Employee("John");
              workers[1] = new Contractor("Mary");
              workers[2] = new Employee("Steve");
 
              for (Person p: workers){
                        p.promote(30);
              }
       }
}

Compare the code of TestPayIncrease2 to TestPayIncrease from the Try It section of Lesson 6. Which one do you like better? I like TestPayIncrease2, which exhibits polymorphic behavior, explained next.

Polymorphism

Polymorphism is easier to understand through an example. Let’s look at the classes Person, Employee, and Contractor from a different angle. The code in Listing 7-4 populates an array, mixing up the instances of the classes Employee and Contractor with hard-coded names. In real life the data about workers usually comes from an external data source. For example, a program could get a person’s work status from the database and instantiate an appropriate concrete class. The class TestPayIncrease2 gives an additional vacation day and attempts to increase the salary or hourly rate of every worker by 30 percent.

Note that even though the loop variable p is of its ancestor’s type Person in Listing 7-4, at every iteration it actually points at either an Employee or a Contractor instance. The actual object type will be evaluated only during the run time. This feature of object-oriented languages is called run-time binding or late binding.

The output of the class TestPayIncrease2 will look as follows:

Promoting a worker...
Giving a day off to John
Increasing salary by 30%. Person's name is John
 Promoting a worker...
Giving a day off to Mary
Sorry, can't increase hourly rate by more than 20%. Person's name is Mary
 Promoting a worker...
Giving a day off to Steve
Increasing salary by 30%. Person's name is Steve

Both classes, Employee and Contractor, were inherited from the same base class, Person. Instead of having different methods for increasing the worker’s compensation based on the worker’s type, we give a polymorphic behavior to the method increasePay(), which applies different business logic depending on the type of the object.

Although it looks as if we’re calling the same method, promote(), on every object from the array workers, this is not the case. Since the actual object type is evaluated during run time, the pay is raised properly according to this particular object’s implementation of the method increasePay(). This is polymorphism in action.

The for loop in the class TestPayIncrease2 will remain the same even if we add some other types of workers inherited from the class Person. For example, to add a new category of worker — a foreign contractor — we’ll have to create a class called ForeignContractor derived from the class Person and implement the method increasePay() there. The class TestPayIncrease2 will keep evaluating the actual type of Person’s descendants during run time and call the proper implementation of the method increasePay().

Polymorphism allows you to avoid using switch or if statements with the expensive operator instanceof, which you used in Lesson 6. Would you agree that even though TestPayIncrease is producing the same results, its code looks pretty ugly compared to TestPayIncrease2? The code in TestPayIncrease works more slowly than the polymorphic version, and its if statement has to be modified every time a new type of worker is added.

Making the Interface Solution Polymorphic

Could you create the polymorphic solution in the example with interfaces from Lesson 6? Yes, you could. Even though the array workers has been declared of type Person, it is populated by the objects that implement the Payable interface.

Instead of casting the objects from this array to either Employee or Contractor, you would cast these objects to Payable and call the increasePay() method without worrying too much about whether the current worker is an employee or a contractor (see Listing 7-5).

download.eps

Listing 7-5: Class TestPayIncreasePoly

import com.practicaljava.lesson6.*;
 
public class TestPayInceasePoly {
 
        public static void main(String[] args) {
 
        Person workers[] = new Person[3];
              workers[0] = new Employee("John");
              workers[1] = new Contractor("Mary");
              workers[2] = new Employee("Steve");
              
              for (Person p: workers){
                 ((Payable)p).increasePay(30);
              }
        }
}

Note that the variable p is cast to Payable first, and only after the method increasePay() can be called. To enforce the sequence of these operations this code has been surrounded with parentheses. The Eclipse IDE comes in handy with its context-sensitive help — when you type the period after the parentheses it readily shows you increasePay() in the list of available methods. I always use this little help: If a method name I’m about to type is not shown there, I must be doing something wrong with the types; otherwise Eclipse would have helped me out.

The other solution that would eliminate the need of casting to Payable is having the class Person implement Payable. In this case the loop from Listing 7-5 would look as follows:

for (Payable p: workers){
   increasePay(30);
}

What can go wrong during the execution of the code from Listing 7-5? What if a developer creates a class called ForeignContractor without implementing the Payable interface, and this error makes it into the array workers? You’ll get a run-time casting error — JVM can’t cast an object to Payable if it doesn’t implement the Payable interface. You’ll have a chance to see this error for yourself in the Try It section.

Interfaces versus Abstract Classes

The next question is when should you use interfaces and when should you use abstract classes. If two or more classes have lots of common functionality, but some methods should be implemented differently, you can create a common abstract ancestor and as many subclasses inheriting this common behavior as needed. Declare in the superclass as abstract those methods that subclasses should implement differently, and implement these methods in subclasses.

If several classes don’t have common functionality but need to exhibit some common behavior, do not create a common ancestor, but have them implement an interface that declares the required behavior. This scenario was not presented in the “Interfaces” section of Lesson 6, but it’s going to be a part of the hands-on exercise in the Try It section of this lesson.

Interfaces and abstract classes are similar in that they ensure that required methods will be implemented according to required method signatures. But they differ in how the program is designed. While abstract classes require you to provide a common ancestor for the classes, interfaces don’t.

Interfaces could be your only option if a class already has an ancestor that cannot be changed. Java doesn’t support multiple inheritance — a class can have only one ancestor. For example, to write Java applets you must inherit your class from the class Applet, or in the case of Swing applets, from JApplet. Here using your own abstract ancestor is not an option.

While using abstract classes, interfaces, and polymorphism is not a must, it certainly improves the design of Java code by making it more readable and understandable to others who may need to work on programs written by you.

Try It

In the first part of the assignment your goal is to break the code from Listing 7-5 to produce the run-time error ClassCastException. In the second part of the assignment you’ll need to rewrite the assignment from Lesson 6 to keep the Payable interface but remove the common ancestor Person.

Lesson Requirements

For this lesson you should have Java installed.

note.ai

You can download the code and resources for this Try It from the book’s web page at www.wrox.com. You can find them in the Lesson7 folder in the download.

Step-by-Step

Part 1

1. In Eclipse open the project Lesson6 — yes, the one from the previous lesson.

2. Create a new class called ForeignContractor, as shown in the following code. Note that this class doesn’t implement the Payable interface:

package com.practicaljava.lesson6;
 
public class ForeignContractor extends Person {
       
       public ForeignContractor(String name){
              super(name);
       }
       public boolean increasePay(int percent) {
       
              System.out.println("I'm just a foreign worker");
              return true;
       }
}

3. Create the class TestPayIncreasePolyError, adding an instance of the ForeignContractor class:

import com.practicaljava.lesson6.*;
 
public class TestPayIncreasePolyError {
 
   public static void main(String[] args) {
 
       Person workers[] = new Person[3];
       workers[0] = new Employee("John");
       workers[1] = new Contractor("Mary");
       workers[2] = new ForeignContractor("Boris");
       
       
              for (Person p: workers){
                        ((Payable)p).increasePay(30);
              }
       }
}

4. Run the program TestPayIncreasePolyError. Observe the output in the console view — you’ll get the run-time error java.lang.ClassCastException on the third element of the array. Note the number 14 — this is the line number of TestPayIncreasePolyError program, which casts each object to the Payable interface.

Increasing salary by 30%. Person's name is John
Sorry, can't increase hourly rate by more than 20%. Person's name is Mary
Exception in thread "main" java.lang.ClassCastException:
com.practicaljava.lesson6.ForeignContractor cannot be cast to 
com.practicaljava.lesson6.Payable
         at TestPayInceasePolyError.main(TestPayInceasePolyError.java:14)

5. Modify the code of TestPayIncreasePolyError, changing the type of the array from Person to Payable and changing the type of the loop variable accordingly:

Payable workers[] = new Payable [3];
workers[0] = new Employee("John");
workers[1] = new Contractor("Mary");
workers[2] = new ForeignContractor("Boris");
               
       for (Payable p: workers){
                 ((Payable)p).increasePay(30);
       }

6. Observe that now you are getting a Java compiler error preventing you from even adding to the array the instance of ForeignContractor because it doesn’t implement Payable. Predicting and preventing run-time errors is a very important task for every software developer, and this subject will be covered in detail in Lesson 13.

Part 2

1. In the project Lesson7 select File New create the new package com.practicaljava.lesson7.tryit.

2. In Eclipse copy the Payable interface from Lesson6 to the package com.practicaljava.lesson7.tryit.

3. In the same package create the class Employee as follows:

package com.practicaljava.lesson7.tryit;
 
public class Employee implements Payable{
       private String name;
       
       public Employee(String name){
              this.name=name;
       }
 
       public boolean increasePay(int percent) {
              System.out.println("Increasing salary by " + percent 
             + "%: " + name); 
             return true;
       }
}

4. In the same package create the class Contractor as follows:

package com.practicaljava.lesson7.tryit;
 
public class Contractor implements Payable {
       
       private String name;
       
       public Contractor(String name){
              this.name=name;
       }
       public boolean increasePay(int percent) {
             if(percent < Payable.INCREASE_CAP){
                     System.out.println("Increasing hourly rate by " + 
                          percent + "%. "); 
                     return true;
              } else {
                     System.out.println("Sorry, can't increase hourly rate by
                        more than " + Payable.INCREASE_CAP + "%: " + name);
                     return false;
              }

Note that neither Employee nor Contractor extends Person any longer. Both classes are free to extend any other classes now, but on the other hand each of them has to declare the variable name and the method getName(), which was done once in the class Person before.

5. Create a class called TestPayIncreaseInterface:

import com.practicaljava.lesson7.tryit.*;
 
public class TestPayIncreaseInterface {
 
      public static void main(String[] args) {
 
        Payable workers[] = new Payable [3];
              workers[0] = new Employee("John");
              workers[1] = new Contractor("Mary");
              workers[2] = new Employee("Steve");
                
                     for (Payable p: workers){
                              ((Payable)p).increasePay(30);
                     }
               } 
} 

6. Run this program — it should produce the following output:

Increasing salary by 30%: John
Sorry, can't increase hourly rate by more than 20%: Mary
Increasing salary by 30%: Steve

To get the sample database files you can download Lesson 7 from the book’s website at www.wrox.com.

cd.ai

Please select Lesson 7 on the DVD with the print book, or watch online at www.wrox.com/go/fainjava to view the video that accompanies this lesson.

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

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