Java – Immutable Classes and Subclasses

java

I'm trying to learn about mutable/immutable classes and I came across this post

Part of the answer provided was:

If you want to enforce immutability, you cannot have subclasses. See
for example java.lang.String, which is a final class for this reason:
To prevent people from subclassing String to make it mutable.

Alright, I understand it, but, how would you handle this issue. Let's say you were given the task of creating 3 Employee classes, Accountant, ITDepartment, and QualityAssurance. Now, you could create an abstract class called Employee with common methods that would be shared by all (employee ID, name, salary etc..), however, your classes are no longer immutable.

Using Java, How then would you solve this problem ? Would you create the 3 classes, make them final, and don't implement an abstract method? (So, no subclassing whatsoever) or would you use an interface, and provide only the getters?

Best Answer

If you want to enforce immutability, you cannot have subclasses.

This is almost true, but not entirely. To restate it:

If you want to enforce immutability, you must ensure that all sub-classes are immutable.

The problem with allowing subclassing is that normally anyone who can author a class can subclass any public non-final class.

But all subclasses must invoke one of their super-class's constructors. Package-private constructors can only be invoked by subclasses in the same package.

If you seal packages so that you control which classes are in your package, you can constrain subclassing. First define a class you want to subclass:

public abstract class ImmutableBaseClass {
  ImmutableBaseClass(...) {
    ...
  }
}

Since all sub-classes have to have access to the super-constructor, you can ensure all the sub-classes in the package you define follow immutable discipline.

public final class ImmutableConcreteClass extends ImmutableBaseClass {
  public ImmutableConcreteClass(...) {
    super(...);
  }
}

To apply this to your example,

public abstract class Employee {
  private final Id id;
  private final Name name;

  // Package private constructor in sub-classable class.
  Employee(Id id, Name name, ...) {
    // Defensively copy as necessary.
  }
}

public final class Accountant extends Employee {
  // Public constructos allowed in final sub-classes.
  public Accountant(Id id, Name name, ...) {
    super(id, name, ...);  // Call to super works from same package.
  }
}

public final class ITWorker extends Employee {
  // Ditto.
  public ITWorker(Id id, Name name, ...) {
    super(id, name, ...);
  }
}