Better Practices for Working with Inner Classes

Contact Us or call 1-877-932-8228
Better Practices for Working with Inner Classes

Better Practices for Working with Inner Classes

It is easiest if inner class objects can always be instantiated from the enclosing class object. You can create a factory method to accomplish this.

Code Sample:

Java-InnerClasses/Demos/FactoryInnerOuter.java
class FactoryOuter {
  FactoryInner[] fi = new FactoryInner[3];
  protected int lastIndex = 0;
  private int x = 0;
  public FactoryOuter(int x) {
    this.x = x;
  }
  public int getX() {
    return x;
  }
  public void addInner(int y) {
    if (lastIndex < fi.length) {
      fi[lastIndex++] = new FactoryInner(y);
    } 
    else throw new RuntimeException("FactoryInner array full");
  }
  public void list() {
    for (int i = 0; i < fi.length; i++) {
      System.out.print("I can see into the inner class where y = " + 
                       fi[i].y + " or call display: ");
      fi[i].display();      
    }
  }
  public class FactoryInner {
    private int y;
    protected FactoryInner(int y) {
      this.y = y;
    }
    public void display() {
      System.out.println("FactoryInner x =  " + x + " and y = " + y);
    }
  }
}
public class FactoryInnerOuter {
  public static void main(String[] args) {
    FactoryOuter fo = new FactoryOuter(1);
    fo.addInner(101);
    fo.addInner(102);
    fo.addInner(103);
    fo.list();
    //fo.addInner(104);
  }
}

For convenience, this file contains both the main class and the FactoryOuter class (with package access). Note that:

  • An instance of FactoryOuter contains a three element array of FactoryInner objects.
  • The addInner method instantiates a FactoryInner object and adds it to the array (note that is still automatically associated with the FactoryOuter instance by the JVM, but we need our own mechanism for keeping track of the inner class instances we create).
    • A better approach would be to use one of the collections classes instead of an array, to avoid running out of room in the array

This is exactly the sort of thing that happens when you obtain an iterator from a collection class. In order to successfully navigate what is most likely a complex internal structure, the object will need access to the private elements. So, an inner class is used, but all you need to know about the object is that it implements the Iterator interface.

Code Sample:

Java-InnerClasses/Demos/PayrollInnerClass/employees/Employee.java
package employees;
import finance.TransactionException;

public class Employee {

---- C O D E   O M I T T E D ----
  private double ytdPay;
  private Payment[] payments = new Payment[12]; 
  private int paymentCount = 0;  

---- C O D E   O M I T T E D ----
  public double getYtdPay() { return ytdPay; }
  
  public Payment createPayment() {
    Payment p = new Payment(payRate);
    payments[paymentCount++] = p;
    return p;
  }

  public void printPaymentHistory() {
    for (Payment p : payments) {
      System.out.println(p);
    }
  }
  
  public class Payment {
    private double amount;
    private boolean posted;
    
    public Payment(double amount) {
      this.amount = amount;
    }

    public boolean process() throws TransactionException {
      if (!posted) {
        ytdPay += amount;
        posted = true;
        System.out.println(getFullName() + " paid " + amount);
        return true;
      } else {
        throw new TransactionException("Transaction already processed");
      }
    }
    
    public String toString() {
      return getFullName() + " payment of " + amount;
    }
    
  } 
}

Payment is an inner class to a simplified Employee, and, as an inner class, has free access to all private elements of Employee. Unlike a standalone payment class, this class can retrieve the employee name from the outer class instance. We also use this access to defer updating the year-to-date amounts until the payment is posted, via the process method.

To get this degree of interaction between two separate classes would be difficult, since it would mean that either:

  1. The ability to update ytdPay would have to be publicly available.
  2. Employee and Payment would have to be in the same package, with updating ytdPay achieved by using package access.

Note that we have also separated the concepts of creating a payment from actually posting it. This gives us better control over transactions - note that a payment cannot be processed twice.

Code Sample:

Java-InnerClasses/Demos/PayrollInnerClass/Payroll.java
import employees.*;
import finance.*;

public class Payroll {
  public static void main(String[] args) {

    Employee.setNextId(22);
    Employee e = new Employee("John", "Doe", 6000.0);
    
    // loop to pay each month
    for (int month = 0; month < 12; month++) {
      Employee.Payment p = e.createPayment();
      try {
        p.process();
        
        // HR error causes attempt to process June paycheck twice
        if (month == 5) p.process();
      }
      catch (TransactionException te) {
        System.out.println(te.getMessage());
      }
      System.out.println("Ytd pay: " + e.getYtdPay());
    }     
    
    System.out.println("Employee Payment History:");
    
    e.printPaymentHistory();
  }
  
}

We have only one employee for simplicity. As we loop for each month, a payment is created for each. We try to process the June payment twice (remember that the array is zero-based, so January is month 0; this matches the behavior of the java.util.Date class) . The second attempt to process the payment should throw an exception which our catch block handles.

We retrieve and print the year-to-date pay each time we process a payment.

At the end, we have the Employee object print the entire payment history created by our calls to the inner class' process method..

Code Sample:

Java-InnerClasses/Demos/PayrollInnerClassInterface/employees/Employee.java
package employees;
import finance.*;

public class Employee {

---- C O D E   O M I T T E D ----
  public class Payment implements Payable {
    private double amount;
    private boolean posted;
    
    public Payment(double amount) {
      this.amount = amount;
    }

    public boolean process() throws TransactionException {
      if (!posted) {
        ytdPay += amount;
        posted = true;
        System.out.println(getFullName() + " paid " + amount);
        return true;
      } else {
        throw new TransactionException("Transaction already processed");
      }
    }
    
    public String toString() {
      return getFullName() + " payment of " + amount;
    }
    
  } 
}

This code goes one step further to create a Payment inner class that implements the Payable interface.

Code Sample:

Java-InnerClasses/Demos/PayrollInnerClassInterface/Payroll.java
import employees.*;
import finance.*;

public class Payroll {
  public static void main(String[] args) {

    Employee.setNextId(22);
    Employee e = new Employee("John", "Doe", 6000.0);
    
    // loop to pay each month
    for (int month = 0; month < 12; month++) {
      Payable p = e.createPayment();
      try {
        p.process();
        
        // HR error causes attempt to process June paycheck twice
        if (month == 5) p.process();
      }
      catch (TransactionException te) {
        System.out.println(te.getMessage());
      }
      System.out.println("Ytd pay: " + e.getYtdPay());
    }     
    
    System.out.println("Employee Payment History:");
    
    e.printPaymentHistory();
  }
  
}

The only difference here is that we declare the variable holding the payments as Payable, hiding the fact that it is an inner class.

Next