Shortly after the
Second Edition of
Josh Bloch's
Effective Java was released, I borrowed a colleague's copy to browse it and consider purchasing a copy of my own despite already owning a copy of the first edition. It did not take me long to realize that the Second Edition was worth purchasing. In particular, the usefulness of the first new item in the Second Edition (Item #2 "Consider a builder when faced with many constructor parameters") struck me. This item addressed many issues I had run into during my Java development in a nice fashion. In this post, I look at the approach described in that chapter and look at how
NetBeans 7.2 provides refactoring support for this approach.
As outlined in Item #2 of the Second Edition of
Effective Java, there are several disadvantages to using constructors with large parameter lists. To help illustrate, I include a code listing below.
Employee.java
package dustin.examples;
/**
* Simple employee class intended to illustrate NetBeans 7.2 and refactoring
* constructor to use Builder pattern as discussed in Item #2 of the Second
* Edition of Joshua Bloch's <em>Effective Java</em>.
*
* @author Dustin
*/
public class Employee
{
private String lastName;
private String middleName;
private String firstName;
private long id;
private int birthYear;
private int birthMonth;
private int birthDate;
private int hireYear;
private int hireMonth;
private int hireDate;
public Employee(
final String newLastName,
final String newMiddleName,
final String newFirstName,
final long newId,
final int newBirthYear,
final int newBirthMonth,
final int newBirthDate,
final int newHireYear,
final int newHireMonth,
final int newHireDate)
{
this.lastName = newLastName;
this.middleName = newMiddleName;
this.firstName = newFirstName;
this.id = newId;
this.birthYear = newBirthYear;
this.birthMonth = newBirthMonth;
this.birthDate = newBirthDate;
this.hireYear = newHireYear;
this.hireMonth = newHireMonth;
this.hireDate = newHireDate;
}
}
Employee
's parameterized constructor includes numerous parameters and this presents a challenge to clients of this class that need to create a new instance of it. This particular constructor is particularly tricky for clients because it has three consecutive
String
parameters and numerous consecutive
int
parameters. It is all too easy for a client to mix up the order of these same-typed parameters in the invocation.
Suppose that business logic is such that an employee should be instantiable with only a last name, first name, and ID provided. In other words, suppose that all attributes of the class are optional except for the three just mentioned. In such a case, with only a constructor like that one above, the client would need to pass null for the middle name String and zeroes or some other number without meaning. The numeric types could be reference types rather than primitives, but the clients would still need to pass null for each optional argument.
One might argue that a three-argument constructor could accepting the three required attributes' values could be supplied and then appropriate
set
methods could be invoked to set each optional attribute. However, this approach has drawbacks. For one, it makes it easier for the client to accidentally instantiate an instance with a bad or partial state. In some cases, it is not desirable to change the state of the object after instantiation, but the presence of the
set
methods make it more mutable than desired. Indiscriminate supplying of set methods has
several drawbacks in the world of concurrency and object-oriented interfaces.
An elegant approach that allows a client to only supply optional parameters that are meaningful without supplying otherwise unnecessary
set
methods is the Builder pattern. NetBeans 7.2 makes it easy to refactor the constructor with the long parameter list in the above example to take advantage of an instance of Builder. The next screen snapshot shows how I can use NetBeans 7.2 to accomplish this by hovering over the parameterized constructor, right-clicking on it, and selecting the option "Replace constructor with Builder...".
When this option is selected a popup similar to that shown in the next screen snapshot appears.
The refactoring tool only allows parameters passed to the constructor being refactored to be set in the builder. I can check the boxes of the optional attributes. When I make an attribute optional without supplying a default value, a warning appears as shown in the next screen snapshot.
If I supply default values (null for the String and zero for the integers that should never be zero for valid values), the warnings go away.
It is worth pointing out here that the refactor tool allows the refactor to continue when only warnings (no errors) are present. An error might be caused, for example, by declaring a package and class name of the builder to be generated with a name used by an existing class.
The "Preview" button can be clicked to see what the class would look like without actually creating the class file. When the "Refactor" button is clicked, a new Java class with the package and class name indicated in the "Builder Class Name" text field is created. The result in this case is shown in the next screen snapshot with the generated code listed in a listing following the snapshot.
NetBeans 7.2-Generated EmployeeBuilder.java
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package dustin.examples;
public class EmployeeBuilder
{
private String newLastName;
private String newMiddleName = null;
private String newFirstName;
private long newId;
private int newBirthYear = 0;
private int newBirthMonth = 0;
private int newBirthDate = 0;
private int newHireYear = 0;
private int newHireMonth = 0;
private int newHireDate = 0;
public EmployeeBuilder()
{
}
public EmployeeBuilder setNewLastName(String newLastName)
{
this.newLastName = newLastName;
return this;
}
public EmployeeBuilder setNewMiddleName(String newMiddleName)
{
this.newMiddleName = newMiddleName;
return this;
}
public EmployeeBuilder setNewFirstName(String newFirstName)
{
this.newFirstName = newFirstName;
return this;
}
public EmployeeBuilder setNewId(long newId)
{
this.newId = newId;
return this;
}
public EmployeeBuilder setNewBirthYear(int newBirthYear)
{
this.newBirthYear = newBirthYear;
return this;
}
public EmployeeBuilder setNewBirthMonth(int newBirthMonth)
{
this.newBirthMonth = newBirthMonth;
return this;
}
public EmployeeBuilder setNewBirthDate(int newBirthDate)
{
this.newBirthDate = newBirthDate;
return this;
}
public EmployeeBuilder setNewHireYear(int newHireYear)
{
this.newHireYear = newHireYear;
return this;
}
public EmployeeBuilder setNewHireMonth(int newHireMonth)
{
this.newHireMonth = newHireMonth;
return this;
}
public EmployeeBuilder setNewHireDate(int newHireDate)
{
this.newHireDate = newHireDate;
return this;
}
public Employee createEmployee()
{
return new Employee(newLastName, newMiddleName, newFirstName, newId, newBirthYear, newBirthMonth, newBirthDate, newHireYear, newHireMonth, newHireDate);
}
}
As the screen snapshot and code listing above indicate, NetBeans 7.2 generated a builder based on the constructor. This was easily done, but there are a few things I don't like about it as much as the builders I have generated by hand. For the builders I have built by hand, I have chosen to have required attributes be part of the builder's constructor so that they must be supplied (I had expected this to happen for the parameters that I did not check the box for optional on). This is shown in the next code listing, which is an adaptation of the generated code shown above. The only thing changed from above is the builder's constructor. It now takes parameters for the
Employee
class's required attributes.
Adapted EmployeeBuilder.java
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package dustin.examples;
public class EmployeeBuilder
{
private String newLastName;
private String newMiddleName = null;
private String newFirstName;
private long newId;
private int newBirthYear = 0;
private int newBirthMonth = 0;
private int newBirthDate = 0;
private int newHireYear = 0;
private int newHireMonth = 0;
private int newHireDate = 0;
public EmployeeBuilder(
final String newBldrLastName, final String newBldrFirstName, final String newBldrMiddleName)
{
this.newLastName = newBldrLastName;
this.newFirstName = newBldrFirstName;
this.newMiddleName = newBldrMiddleName;
}
public EmployeeBuilder setNewLastName(String newLastName)
{
this.newLastName = newLastName;
return this;
}
public EmployeeBuilder setNewMiddleName(String newMiddleName)
{
this.newMiddleName = newMiddleName;
return this;
}
public EmployeeBuilder setNewFirstName(String newFirstName)
{
this.newFirstName = newFirstName;
return this;
}
public EmployeeBuilder setNewId(long newId)
{
this.newId = newId;
return this;
}
public EmployeeBuilder setNewBirthYear(int newBirthYear)
{
this.newBirthYear = newBirthYear;
return this;
}
public EmployeeBuilder setNewBirthMonth(int newBirthMonth)
{
this.newBirthMonth = newBirthMonth;
return this;
}
public EmployeeBuilder setNewBirthDate(int newBirthDate)
{
this.newBirthDate = newBirthDate;
return this;
}
public EmployeeBuilder setNewHireYear(int newHireYear)
{
this.newHireYear = newHireYear;
return this;
}
public EmployeeBuilder setNewHireMonth(int newHireMonth)
{
this.newHireMonth = newHireMonth;
return this;
}
public EmployeeBuilder setNewHireDate(int newHireDate)
{
this.newHireDate = newHireDate;
return this;
}
public Employee createEmployee()
{
return new Employee(newLastName, newMiddleName, newFirstName, newId, newBirthYear, newBirthMonth, newBirthDate, newHireYear, newHireMonth, newHireDate);
}
}
The code used to call the adapted builder shown in the last code listing is exemplified in the following code listing. This code listing demonstrates how readable ("fluent" in the more trendy vernacular) the client code is with the builder and demonstrates that the object is never in a partial or inconsistent state.
package dustin.examples;
import java.util.Calendar;
/**
* Example calling adapted employee builder to get an instance of employee.
*
* @author Dustin
*/
public class Main
{
public static void main(final String[] arguments)
{
final Employee employee =
new EmployeeBuilder("Coyote", "Wile", "E.")
.setNewBirthMonth(Calendar.SEPTEMBER)
.setNewBirthDate(17)
.setNewBirthYear(1949)
.createEmployee();
}
}
The most significant drawbacks to the builder implementation are potential performance impacts of extra object instantiation (probably not an issue for most Java applications) and the extra code verbosity.
Although I don't show it here, the other thing I like to do with my builder implementations is to include them as nested classes in the class they build. This is how the code example in the second item of the Second Edition of
Effective Java implements it, but I don't see anyway in NetBeans 7.2 to generate the builder as a nested class rather than as a standalone class.