I'm studying Effective Java and in Item 5 of the Book, Joshua Bloch talks about the avoidance of creating unnecessary objects. An example demonstrates mutable Date objects that are never modified once their values have been computed.
Here the 'bad practice':
public Person(Date birthDate) {
this.birthDate = new Date(birthDate.getTime());
}
// DON'T DO THIS!
public boolean isBabyBoomer() {
// Unnecessary allocation of expensive object
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
Date boomStart = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
Date boomEnd = gmtCal.getTime();
return birthDate.compareTo(boomStart) >= 0
&& birthDate.compareTo(boomEnd) < 0;
}
The isBabyBoomer method unnecessarily creates a new Calendar, TimeZone, and two Date instances each time it is invoked - and that clearly makes sense to me.
And here the improved code:
public Person(Date birthDate) {
this.birthDate = new Date(birthDate.getTime());
}
/**
* The starting and ending dates of the baby boom.
*/
private static final Date BOOM_START;
private static final Date BOOM_END;
static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();
}
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START) >= 0
&& birthDate.compareTo(BOOM_END) < 0;
}
Calendar, TimeZone, and Date instances are created only once, when it is initialized.
Bloch explains, that this results in significant performance gains if the method isBabyBoomer()
is invoked frequently.
On his machine:
Bad Version: 32,000 ms for 10 million invocations
Improved Version: 130ms for 10 million invocations
But when I run the examples on my System the performance is exactly the same (14ms). Is that a compiler feature that the instances are only created once ?
Edit:
Here is my benchmark:
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
cal.set(1960, Calendar.JANUARY, 1, 1, 1, 0);
Person p = new Person(cal.getTime());
long startTime = System.nanoTime();
for (int i = 0; i < 10000000; i++) {
p.isBabyBoomer();
}
long stopTime = System.nanoTime();
long elapsedTime = stopTime - startTime;
double mseconds = (double) elapsedTime / 1000000.0;
System.out.println(mseconds);
}
Cheers, Markus
Your benchmark is wrong. With the newest Java 7 and a proper warmup I get a dramatic difference between the two methods:
Here is the full runnable code:
Your question turned out to be just another case of a wrong microbenchmark.
However, in some special cases (mostly with simple data-holding classes), there really is a JVM optimization that discards most of object instantiations. You might want to look at the links below.
The methods described there are obviously not applicable in your case, but it might make the difference in some other strange cases where object instantiation just doesn't seem to taky any time. So remember this for when you actually come across the working example of your question:
The most relevant part: