Wednesday, October 24, 2012

Java/NetBeans: Overridable Method Call in Constructor

I wrote about the NetBeans hint "Overridable Method Call in Constructor" in the blog post Seven Indispensable NetBeans Java Hints. In this post, I look at why having an overridable method called from a parent class's constructor is not a good idea.

The next class, Employee, is a contrived example of a class in which the extensible class's constructor calls an overridable method (setSalaryRange()).

Employee.java
package dustin.examples.overridable;

/**
 * Simple employee class that is intended to be a parent of a specific type of
 * employee class. The main purpose of this class is to demonstrate the
 * insidious dangers associated with a constructor calling an overridable method.
 * 
 * @author Dustin
 */
public class Employee
{
   private String lastName;
   private String firstName;
   private JobTitle jobTitle;
   protected int minWeeklySalary;
   protected int maxWeeklySalary;

   public enum JobTitle
   {
      CHIEF_EXECUTIVE_OFFICER("CEO"),
      COMPUTER_SCIENTIST("Computer Scientist");

      private String displayableTitle;

      JobTitle(final String newDisplayableTitle)
      {
         this.displayableTitle = newDisplayableTitle;
      }

      public String getDisplayableTitle()
      {
         return this.displayableTitle;
      }
   }

   public Employee(
      final String newLastName, final String newFirstName, final JobTitle newJobTitle)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.jobTitle = newJobTitle;
      setSalaryRange();
   }

   public void setSalaryRange()
   {
      this.minWeeklySalary = 5;
      this.maxWeeklySalary = 10;
   }

   @Override
   public String toString()
   {
      return  this.firstName + " " + this.lastName + " with title '"
            + this.jobTitle.getDisplayableTitle()
            + "' and with a salary range of $" + this.minWeeklySalary + " to $"
            + this.maxWeeklySalary + ".";
   }
}

NetBeans flags the existence of an overridable method called from a constructor as shown in the next screen snapshot (NetBeans 7.3 in this case):

To demonstrate a common problem associated with overridable methods called in a constructor, a child class is needed. That is shown next with the code listing for ComputerScientist, which extends Employee.

ComputerScientist.java
package dustin.examples.overridable;

/**
 * Class representing a specific type of employee (computer scientist), but its
 * real purpose is to demonstrate how overriding a method called in the parent
 * class's constructor leads to undesired behavior.
 * 
 * @author Dustin
 */
public class ComputerScientist extends Employee
{
   private final int MIN_CS_WEEKLY_SALARY_IN_DOLLARS = 1000;
   private static final int MAX_CS_WEEKLY_SALARY_IN_DOLLARS = 60000;

   private int marketFactor = 1;

   public ComputerScientist(
      final String newLastName, final String newFirstName, final int newMarketFactor)
   {
      super(newLastName, newFirstName, JobTitle.COMPUTER_SCIENTIST);
      this.marketFactor = newMarketFactor;
   }

   @Override
   public void setSalaryRange()
   {
      this.minWeeklySalary = MIN_CS_WEEKLY_SALARY_IN_DOLLARS * this.marketFactor;
      this.maxWeeklySalary = MAX_CS_WEEKLY_SALARY_IN_DOLLARS * this.marketFactor;
   }
}

Finally, a simple test driving application is required to run this example. That is shown in the next simple executable class (Main.java).

Main.java
package dustin.examples.overridable;

import dustin.examples.overridable.Employee.JobTitle;
import static java.lang.System.out;

/**
 * Simple driver of the demonstration of why calling an overridable method in
 * the constructor of an extendible class is a bad idea.
 * 
 * @author Dustin
 */
public class Main
{
   public static void main(final String[] arguments)
   {
      final ComputerScientist cs = new ComputerScientist("Flintstone", "Fred", 5);
      final Employee emp = new Employee("Rubble", "Barney", JobTitle.CHIEF_EXECUTIVE_OFFICER);

      out.println(cs);
      out.println(emp);
   }
}

One might expect the Computer Scientist, Fred Flintstone, to earn a weekly salary in the range of $1,000 to $60,000. However, that is not what is shown when the simple main application is executed (command line output and NetBeans output shown).

The reason the Computer Scientist's salary range is not correct is that the parent class's (Employee's) constructor must first be run before the extending class (ComputerScientist) is completely instantiated. The child class does override the setSalaryRange() method, but this overridden implementation depends on an instance variable (marketFactor) that is not yet initialized in the child instance when the parent's constructor calls this child class's overridden method.

There are multiple ways to avoid this problem. Perhaps the best and easiest approaches are those recommended by the NetBeans hint that flagged this issue.

As the screen snapshot above shows, NetBeans provides four easy and effective ways to deal with the issue of an overridable method called from the constructor of a class. Because the issue involves an "overridable" method and a child class that is not fully instantiated when that overridable method is invoked during parent's constructor, an obvious tactic is to make the parent class final so that it cannot be extended. This obviously will only work for new classes that don't have classes extending them. I have found that Java developers often don't put a lot of consideration into making a class final or planning for it to be extensible, but this is an example of where such consideration is worthwhile.

When it is not practical to make the parent class final, NetBeans offers three other approaches for addressing the problem of an overridable method called from the parent class constructor. Even if the class cannot be final, that method can have the final modifier applied to it so that the constructor is no longer calling an "overridable" method. This allows a child class to still extend the parent class, but it cannot override that method and so won't have an implementation that depends on instance level variables that have not yet been initialized.

The other two ways shown that NetBeans uses to handle the issue of an overridable method being called from a constructor likewise focus on making that invoked method not overridable. In these other two cases, this is accomplished by making the invoked method static (class level rather than instance level) or by making the method private (but then not accessible at all to the child class).

In my particular example above, another way to fix the specific issue would have been to convert the instance-level marketFactor variable into a class-level (static) variable because that would move its initialization forward enough to be used in the invocation of the overridden method. The problem with this method is that there is still an overridable method invoked from the parent constructor and that method can have more than one state issue to worry about.

In general, I try to be very careful about a constructor calling methods as part of an instance's instantiation. I prefer to use a static factory that constructs an object and calls static methods to set that instance appropriately. It is always wise to be cautious with implementation inheritance and this issue of overridable methods called from a constructor is just another in the list of reasons why caution is warranted.

My example shown in this post is relatively simple and it is easy to figure out why the results were not as expected. In a much larger and more complicated example, it might be more difficult to find this and it can lead to pernicious bugs. NetBeans helps tremendously by warning about overridable methods called from constructors so that appropriate action can be taken. If a class is not extended and there are no plans to extend it, then making the class final is easy and appropriate. Otherwise, NetBeans presents other options for ensuring that all methods called from a constructor are not overridable.

1 comment:

Turbo said...

Awesome article of something I didn't know yet.