Search code examples
javaimmutabilityfinal

Best design approach for creating Immutable Class


I am reading about the specific guidelines that needs to be followed while creating Immutable Class in Effective Java.

I read that In Immutable class method should not allowed to be overridden otherwise overridden method may change the behaviour of method. Following are the design approaches available in java to solve this problem :-

  1. We can mark class final but as per my understanding, it has a one disadvantage that it makes the class inextensible.

  2. Secondly is to make individual methods final but I can not get other disadvantage besides that we need to individually mark each method as final in order to prevent overridding.

  3. As per book,better approach is to make the constructor private or package-private and provide public static factory method for creating object.

My question is: Even if we include private or default constructor in the class, it cannot be extended anymore in same package (in other package in case of package-private constructor), it has a same problem which the first one had. How is it considered as the better approach than the previous ones?


Solution

  • Providing a static factory method gives you room to implement the Flyweight Pattern.

    They're stating that you should hide the possibility of creating a new object using a constructor, and should rather make a call to a method which checks if an object with similar state exists in the "object pool" (a map filled with objects waiting to be re-used). Not re-using immutable objects is a waste of memory; this is why String literals are encouraged, and new String() is shunned (unless needed).

    class ImmutableType {
        private static final Map<Definition, ImmutableType> POOL = new HashMap<>();
    
        private final Definition definition;
    
        private ImmutableType(Definition def) {
             definition = def;
        }
    
        public static ImmutableType get(Definition def) {
             if(POOL.contains(def))
                  return POOL.get(def);
            else {
                  ImmutableType obj = new ImmutableType(def);
                  POOL.put(def, obj);
    
                  return obj;
            }
        }
    }
    

    Definition stores the state of the ImmutableType. If a type with the same definition already exists in the pool, then re-use it. Otherwise, create it, add it to the pool then return it as the value.

    As for the statement about marking the class final, immutable types should not be extensible in the first place (to avoid possibly modifying behavior). Marking every method final is just crazy for immutable classes.