My first code
blog. A review and my understanding of the book Effective Java by Joshua Bloch.
The book as it's
name suggests focuses on the effective use of Java. It contains 78 items each
of which capture the best practices to be followed. It contains code examples
illustrating many design patterns and idioms. I have tried to capture the
essence of the items by giving my own examples in this blog.
This book is all
about writing programs that are clear, correct, usable, robust, flexible and
maintainable.
Chapter 1 :
Creating and destroying objects
We all know that objects
are the core of Java. But we need to be very miserly when creating objects.
Creating a lot of unnecessary objects leads to various issues like unclean code
and low performance.
Under this
chapter author gives 7 suggestions for better ways to create objects, maintain
them and carefully destroy them.
Item 1
: Using static factory methods as an alternative to constructors.
Static factory
method is one which returns an instance of the class. Here we need to note the
fact that it just returns an instance and does not create a new object every
time it is invoked unlike in a constructor.
Let me explain
the motivation behind this item with an example.
Consider the
class RandomIntGenerator. As its name suggests it is used to
generate random integers.
1
2
3
4
5
6
| public class RandomIntGenerator {
private final int min;
private final int max;
public int next() {...}
}
|
It has two
attributes min and max and a random integer between these 2 values is generated.
These values have to be initialized in the constructor since they are declared
as final so we have the following constructor.
1
2
3
4
| public RandomIntGenerator(int min, int max) {
this.min = min;
this.max = max;
}
|
Now what if you
only had a value for min and you wanted a random integer between that value and
the highest possible value for integers? We can have another constructor as
follows:
1
2
3
4
| public RandomIntGenerator(int min) {
this.min = min;
this.max = Integer.MAX_VALUE;
}
|
Similarly what if
you only had a value for max and wanted an integer between that value and the
smallest possible integer? You might think of having a third constructor as
follows:
1
2
3
4
| public RandomIntGenerator(int max) {
this.min = Integer.MIN_VALUE;
this.max = max;
}
|
But we will get a
compilation error saying Duplicate method because there can be
only one constructor with a particular signature.
So here is where static
factory methods come into picture.
In each of the above 3 cases we can have static factory methods to create an instance of the RandomIntGenerator class as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public class RandomIntGenerator {
private final int min;
private final int max;
private RandomIntGenerator(int min, int max) {
this.min = min;
this.max = max;
}
public static RandomIntGenerator between(int max, int min) {
return new RandomIntGenerator(min, max);
}
public static RandomIntGenerator biggerThan(int min) {
return new RandomIntGenerator(min, Integer.MAX_VALUE);
}
public static RandomIntGenerator smallerThan(int max) {
return new RandomIntGenerator(Integer.MIN_VALUE, max);
}
public int next() {...}
}
|
As we can see static factory methods have very clear names which helps anyone reading the code to understand why it is used. Also by making the constructor private we are ensuring that no unnecessary instantiations have been done.
Continuing with our example, now let us suppose we want random generators for other datatypes as well. So a good solution would be to have an interface.
1
2
3
| public interface RandomGenerator<T> {
T next();
}
|
So now our implementation of RandomIntGenerator changes as follows
1
2
3
4
5
6
7
8
9
10
11
| class RandomIntGenerator implements RandomGenerator<Integer> {
private final int min;
private final int max;
RandomIntGenerator(int min, int max) {
this.min = min;
this.max = max;
}
public Integer next() {...}
}
|
Similarly we can have a random string generator as follows
1
2
3
4
5
6
7
8
9
| class RandomStringGenerator implements RandomGenerator<String> {
private final String prefix;
RandomStringGenerator(String prefix) {
this.prefix = prefix;
}
public String next() {...}
}
|
Here notice that we haven't used the public keyword to define the above classes. So it will be package-private by default and this makes it impossible for any client outside of their package to instantiate these generators.
Once again we have static factory methods to the rescue. We can provide public static factory methods to create instances of the above generators and bundle all these into a utility class. Since it is a utility class and it does not make sense to instantiate it we can make the constructor private.
1
2
3
4
5
6
7
8
9
10
11
12
| public final class RandomGenerators {
// Suppresses default constructor, ensuring non-instantiability.
private RandomGenerators() {}
public static final RandomGenerator<Integer> getIntGenerator() {
return new RandomIntGenerator(Integer.MIN_VALUE, Integer.MAX_VALUE);
}
public static final RandomGenerator<String> getStringGenerator() {
return new RandomStringGenerator("");
}
}
|
So the main idea here is that the implementation classes are hidden and the clients interact only through the interface. This allows a lot of flexibility to add new implementations or change existing ones without the clients noticing the change.
But is it really an all pros and no cons scenario? Not exactly.
Since we are suppressing the constructors by making it private we cannot extend them. But then again we can always use composition which many Java authors suggest is better than using inheritance.
Another problem might have to do with readability or more so with familiarity to the traditional way of creating instances using the new() keyword. But just like any other practice it will take some time to get adapted.
In summary it is always a good habit to give preference to static factory methods over public constructors.