Immutable class using static factories

812 Views Asked by At

I'm studying Effective Java by Joshua Bloch, where he explains about different ways of achieving immutable class. To prevent subclassing, one way is to use final. The more sophisticated way for it is to make constructor private, thus preventing outside access, and using static factories for object creation.

However, I don't understand this statement:

public class Complex {
    private final double re;
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public static Complex valueOf(double re, double im) {
        return new Complex(re, im);
    }
}

It is most flexible because it allows the use of multiple package-private implementation classes.

I understand that it's not possible for an outside client to subclass it in absence of a public/protected constructor, but do not understand what is being conveyed by the term 'multiple package private implementation classes'.

NOTE: This is Item 15 (Minimize mutability) in Effective Java.

2

There are 2 best solutions below

1
On BEST ANSWER

As far as I remember Joshua then talks about EnumSet (but I do not remember context where he mentions it).

EnumSet is abstract and also has static methods of, noneOf, etc. There are two classes extending EnumSet: JumboEnumSet and RegularEnumSet.

You cannot use them directly because they are package-private (no public keyword):

class RegularEnumSet<E extends Enum<E>> extends EnumSet<E>
class JumboEnumSet<E extends Enum<E>> extends EnumSet<E>

java.util package only can use them directly (if we do not speak about reflection or some other techinques).

You simply use static methods of EnumSet and it returns some subclass of EnumSet you should not be aware of.

Take a look at the noneOf method implementation:

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
    Enum<?>[] universe = getUniverse(elementType);
    if (universe == null)
        throw new ClassCastException(elementType + " not an enum");

    if (universe.length <= 64)
        return new RegularEnumSet<>(elementType, universe);
    else
        return new JumboEnumSet<>(elementType, universe);
}

Here is the multiple package-private implementations

5
On

The only way a class cannot be subclassed is if the class is marked as final. Since this class is not final, then it can be subclassed, like this:

public class Complex {

    private final double re;
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    //methods...

    static class SubComplex1 extends Complex {

        private SubComplex1(double re, double im, double x) {
            super(re, im);
            //more elements...
        }
        //you can define/override methods here
    }

    public static Complex valueOf(double re, double im, double x) {
        return new SubComplex1(re, im, x);
    }
}