Effective Java: Second Edition
Author: Joshua Bloch
Version: Amazon Kindle
Alternate: Free PDF Version
Effective Java 2nd Edition is an excellent book for seasoned Java programmers. No matter what your skill level, from rocket scientist to application programmer to interface programmer if you’re programming with core java you need to read this book. Especially relevant and effective for seasoned programmers as it primarily addresses concepts that are now considered antiquated. The Kindle version is excellent with inline links to examples. It’s simple and convenient to select the inline link, jump to the example in another location and then go back. The Kindle version makes excellent use of this capability when used on the Kindle Fire which was my reading platform. I’ve included a link to the PDF version hosted by Prentice Hall which is currently available for free. I would still recommend buying the Kindle version especially if you have a Fire or iPad as it’s more convenient and useful.
Relevant topics in the book force Java programmers to reexamine how we’ve been programming for the last several years. As an example most of us have been using the JavaBeans pattern for mutable objects. Here’a small class I made to demonstrate:
public class MutableObjectExample {
private String testStr = "";
public MutableObjectExample() {
}
/**
* @return the testStr
*/
public String getTestStr() {
return testStr;
}
/**
* @param testStr the testStr to set
*/
public void setTestStr(String testStr) {
this.testStr = testStr;
}
}
This is a fairly typical example for creating a mutable object with getters/setters. I’m not going to go into the debate on the evils of getters/setters but will leave that for a follow-up post. Most programmers in commercial environments primarily work with mutable objects unless they’re working with multi-threaded applications or exposing their API to the outside world.
Let’s consider how a programmer who works primarily with mutable objects would create an immutable object:
public class ImmutableOjectExample {
private final double topBorder;
private final double bottomBorder;
private final double leftBorder;
private final double rightBorder;
public ImmutableOjectExample(double topBorder,
double bottomBorder,
double leftBorder,
double rightBorder) {
this.topBorder = topBorder;
this.bottomBorder = bottomBorder;
this.leftBorder = leftBorder;
this.rightBorder = rightBorder;
}
public void printPercentDiff() {
if (rightBorder > leftBorder)
System.out.printf("Left border is %5.3f percent of the right border. %n", (leftBorder/rightBorder));
else if (leftBorder > rightBorder)
System.out.printf("Right border is %5.3f percent of the left border. %n", (rightBorder/leftBorder));
if (bottomBorder > topBorder)
System.out.printf("Top border is %5.3f percent of the bottom border. %n", (topBorder/bottomBorder));
else if (topBorder > bottomBorder)
System.out.printf("Bottom border is %5.3f percent of the top border. %n", (bottomBorder/topBorder));
}
}
There’s some obvious problems with this. The primary issue with this approach is you have to know and remember the order and values of the variables being passed into the constructor. The BuilderFactory won’t take care of this with the immutable parts of the class so no real savings there. But now let’s add a mutator to the above class which no longer makes this an immutable object.
public class ImmutableOjectExample {
private final double topBorder;
private final double bottomBorder;
private final double leftBorder;
private final double rightBorder;
private double offset;
public ImmutableOjectExample(double topBorder,
double bottomBorder,
double leftBorder,
double rightBorder) {
this.topBorder = topBorder;
this.bottomBorder = bottomBorder;
this.leftBorder = leftBorder;
this.rightBorder = rightBorder;
}
public void printPercentDiffWOffset() {
if ((rightBorder-offset) > leftBorder)
System.out.printf("Left border is %5.3f percent of the right border. %n", (leftBorder/rightBorder));
else if ((leftBorder-offset) > rightBorder)
System.out.printf("Right border is %5.3f percent of the left border. %n", (rightBorder/leftBorder));
if ((bottomBorder-offset) > topBorder)
System.out.printf("Top border is %5.3f percent of the bottom border. %n", (topBorder/bottomBorder));
else if ((topBorder-offset) > bottomBorder)
System.out.printf("Bottom border is %5.3f percent of the top border. %n", (bottomBorder/topBorder));
}
/**
* @return the offset
*/
public double getOffset() {
return offset;
}
/**
* @param offset the offset to set
*/
public void setOffset(double offset) {
this.offset = offset;
}
}
Now we have an issue as our class is no longer immutable. The Builder design pattern takes care of this for us however. Reconstructing the above code using the pattern yields:
public class ImmutableOjectExample {
private final double topBorder;
private final double bottomBorder;
private final double leftBorder;
private final double rightBorder;
private final double offset;
public ImmutableOjectExample(ImmObjExBuilder builder) {
this.topBorder = builder.topBorder;
this.bottomBorder = builder.bottomBorder;
this.leftBorder = builder.leftBorder;
this.rightBorder = builder.rightBorder;
this.offset = builder.offset;
}
public static class ImmObjExBuilder {
private final double topBorder;
private final double bottomBorder;
private final double leftBorder;
private final double rightBorder;
private double offset;
public ImmObjExBuilder(double topBorder,
double bottomBorder,
double leftBorder,
double rightBorder) {
this.topBorder = topBorder;
this.bottomBorder = bottomBorder;
this.leftBorder = leftBorder;
this.rightBorder = rightBorder;
}
/* Notice here that we're returning an instance of this internal class which is public */
public ImmObjExBuilder offset(double offset) {
this.offset = offset;
return this;
}
/* the build method is critical as it returns our new outer object */
public ImmutableOjectExample build() {
return new ImmutableOjectExample(this);
}
}
public void printPercentDiffWOffset() {
if ((rightBorder-offset) > leftBorder)
System.out.printf("Left border is %5.3f percent of the right border. %n", (leftBorder/rightBorder));
else if ((leftBorder-offset) > rightBorder)
System.out.printf("Right border is %5.3f percent of the left border. %n", (rightBorder/leftBorder));
if ((bottomBorder-offset) > topBorder)
System.out.printf("Top border is %5.3f percent of the bottom border. %n", (topBorder/bottomBorder));
else if ((topBorder-offset) > bottomBorder)
System.out.printf("Bottom border is %5.3f percent of the top border. %n", (bottomBorder/topBorder));
}
public static void main(String[] args) {
//Calling code now looks like this
ImmutableOjectExample immObjEx = new ImmutableOjectExample.ImmObjExBuilder(
10, 15, 20, 19).offset(5).build();
//now we have an immutable oject again
immObjEx.printPercentDiffWOffset();
//the offset which was previously an open mutator cannot be set after the object has been
//initialized
//following with throw a compile error
//immObjEx.offset = 5.1;
}
}
Now we still have the problem with those pesky constructor values. It’s far too easy to make a mistake when adding in the values. In reality we would probably be passing in an object of some sort.
Either way we’ve demonstrated the use of a Builder design pattern and what it can be used for, mainly when programming with an external API exposed to others or when creating multi-threaded applications and you want to restrict certain parts of an object from change. This doesn’t have to be the only use cases however. It’s argued in Effective Java that programmers should create as few mutators as possible which makes sense. There’s a fine line however between restricting too much and making code difficult to access and keeping code safe.
That’s it for now. I’ll review other chapters in the book when I get a chance.


