Chapter 7: Polymorphism in Inheritance and Interfaces: BankAccount


Polymorphism and Inheritance

In Java, the type of a variable does not completely determine the type of the object to which it refers. For example, a variable of type BankAccount can hold a reference to an actual BankAccount object or a subclass object such as SavingsAccount. You already encountered this phenomenon with variables whose type was an interface. A variable whose type is Measurable holds a reference to an object of a class that implements the Measurable interface, perhaps a Coin object or an object of an entirely different class.

What happens when you invoke a method? For example,

BankAccount anAccount = new CheckingAccount();
anAccount.deposit(1000);

Which deposit method is called? The anAccount parameter has type BankAccount, so it would appear as if BankAccount.deposit is called. On the other hand, the CheckingAccount class provides its own deposit method that updates the transaction count. The anAccount field actually refers to an object of the subclass CheckingAccount, so it would be appropriate if the CheckingAccount.deposit method were called instead.

In Java, method calls are always determined by the type of the actual object, not the type of the object reference. That is, if the actual object has the type CheckingAccount, then the CheckingAccount.deposit method is called. It does not matter that the object reference is stored in a field of type BankAccount. The ability to refer to objects of multiple types with varying behavior is called polymorphism .

If polymorphism is so powerful, why not store all account references in variables of type Object? This does not work because the compiler needs to check that only legal methods are invoked. The Object type does not define a deposit method—the BankAccount type (at least) is required to make a call to the deposit method.

Have another look at the transfer method to see polymorphism at work. Here is the implementation of the method:


public void transfer(double amount, BankAccount other)
{
   withdraw(amount);
   other.deposit(amount);
}


Suppose you call

anAccount.transfer(1000, anotherAccount);

Two method calls are the result:

anAccount.withdraw(1000);
anotherAccount.deposit(1000);

Depending on the actual types of anAccount and anotherAccount, different versions of the withdraw and deposit methods are called.

If you look into the implementation of the transfer method, it may not be immediately obvious that the first method call

withdraw(amount);

depends on the type of an object. However, that call is a shortcut for

this.withdraw(amount);

The this parameter holds a reference to the implicit parameter, which can refer to a BankAccount or a subclass object.

The following program calls the polymorphic withdraw and deposit methods.
Manually calculate what the program should print on edmodo.com for each account balance, and confirm that the correct methods have in fact been called.

AccountTester is the driver class:

  /**
     This program tests the BankAccount class and
     its subclasses.
  */
  public class AccountTester
  {
     public static void main(String[] args)
     {
        SavingsAccount momsSavings
             = new SavingsAccount(0.5);

       CheckingAccount harrysChecking
             = new CheckingAccount(100);

       momsSavings.deposit(10000);

       momsSavings.transfer(2000, harrysChecking);
       harrysChecking.withdraw(1500);
       harrysChecking.withdraw(80);

       momsSavings.transfer(1000, harrysChecking);
       harrysChecking.withdraw(400);

       // Simulate end of month
       momsSavings.addInterest();
       harrysChecking.deductFees();

       System.out.println("Mom\'s savings balance: "
             + momsSavings.getBalance());
       System.out.println("Expected: ????");

       System.out.println("Harry\'s checking balance: "
             + harrysChecking.getBalance());
       System.out.println("Expected: ????");
    }
 }

BankAccount Class:

  /**
     A bank account has a balance that can be changed by
     deposits and withdrawals.
  */
  public class BankAccount
  {
     /**
        Constructs a bank account with a zero balance.
     */
    public BankAccount()
    {
       balance = 0;
    }

    /**
       Constructs a bank account with a given balance.
       @param initialBalance the initial balance
    */
    public BankAccount(double initialBalance)
    {
       balance = initialBalance;
    }

    /**
       Deposits money into the bank account.
       @param amount the amount to deposit
    */
    public void deposit(double amount)
    {
       balance = balance + amount;
    }

    /**
       Withdraws money from the bank account.
       @param amount the amount to withdraw
    */
    public void withdraw(double amount)
    {
       balance = balance - amount;
    }

    /**
       Gets the current balance of the bank account.
       @return the current balance
    */
    public double getBalance()
    {
       return balance;
    }

    /**
       Transfers money from the bank account to another account.
       @param amount the amount to transfer
       @param other the other account
    */
    public void transfer(double amount, BankAccount other)
    {
       withdraw(amount);
       other.deposit(amount);
    }

    private double balance;
 }

CheckingAccount Class derived from BankAccount:

  /**
     A checking account that charges transaction fees.
  */
  public class CheckingAccount extends BankAccount
  {
     /**
        Constructs a checking account with a given balance.
        @param initialBalance the initial balance
     */
    public CheckingAccount(double initialBalance)
    {
       // Construct superclass
       super(initialBalance);

       // Initialize transaction count
       transactionCount = 0;
    }

    public void deposit(double amount)
    {
       transactionCount++;
       // Now add amount to balance
       super.deposit(amount);
    }

    public void withdraw(double amount)
    {
       transactionCount++;
       // Now subtract amount from balance
       super.withdraw(amount);
    }

    /**
       Deducts the accumulated fees and resets the
       transaction count.
    */
    public void deductFees()
    {
       if (transactionCount > FREE_TRANSACTIONS)
       {
          double fees = TRANSACTION_FEE *
                (transactionCount - FREE_TRANSACTIONS);
          super.withdraw(fees);
       }
       transactionCount = 0;
    }

    private int transactionCount;

    private static final int FREE_TRANSACTIONS = 3;
    private static final double TRANSACTION_FEE = 2.0;
 }



SavingsAccount derived from BankAccount:

 /**
   An account that earns interest at a fixed rate.
 */
 public class SavingsAccount extends BankAccount
 {
   /**
      Constructs a bank account with a given interest rate.
      @param rate the interest rate
   */
   public SavingsAccount(double rate)
   {
      interestRate = rate;
   }

   /**
      Adds the earned interest to the account balance.
   */
   public void addInterest()
   {
      double interest = getBalance() * interestRate / 100;
      deposit(interest);
   }

   private double interestRate;
}


Look up the definition of the standard Comparable interface in the API documentation. Modify the DataSet class to accept Comparable objects. With this interface, it is no longer meaningful to compute the average. The DataSet class should record the minimum and maximum data values. Test your modified DataSet class by adding a number of String objects. (The String class implements the Comparable interface.)

Complete the following class in your solution:

DataSet Class takes objects of classes that implement the Comparable interface:

/**
   Computes the minimum and maximum of a set of Comparable values.
*/
public class DataSet
{
   /**
      Constructs an empty data set.
   */
   public DataSet()
   {
      . . .
   }

   /**
      Adds a data value to the data set.
      @param x a data value
   */
   public void add(Comparable x)
   {
      . . .
   }

   /**
      Gets the largest of the added data.
      @return the maximum or null if no data has been added
   */
   public Comparable getMaximum()
   {
      . . .
   }

   /**
      Gets the largest of the added data.
      @return the maximum or null if no data has been added
   */
   public Comparable getMinimum()
   {
      . . .
   }

   . . .
}


Use the following class as your tester class:

/**
   This program demonstrates the use of the DataSet that accepts
   instances of the Comparable interface.
*/
public class DataSetTester
{
   public static void main(String[] args)
   {
      DataSet data = new DataSet();
   
      data.add("Helen");
      data.add("Andy");
      data.add("Robert");
      data.add("Vicky");

      Comparable max = data.getMaximum();
      Comparable min = data.getMinimum();
      System.out.println("Maximum: " + max);
      System.out.println("Expected: Vicky");
      System.out.println("Minimum: " + min);
      System.out.println("Expected: Andy");
   }
}

Assignments:
1. From the classes discussed above, draw the UML class diagram and show/demonstrate late binding in inheritance for the BankAccount classes.

  1. Show/demonstrate late binding in inheritance for the StaffCrew classes.
    abstract public class StaffCrew
    Volunteer extends StaffCrew
    FullTimeStaff extends StaffCrew
    Supervisor extends FullTimeStaff
    PartTime extends FullTimeStaff