9

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?

Community
  • 1
  • 1
  • 1
    Related: [Why would one declare an immutable class final in Java?](http://stackoverflow.com/questions/12306651/why-would-one-declare-an-immutable-class-final-in-java). – Mick Mnemonic Jul 03 '15 at 21:39
  • Even an interface doesn't feel right to me. Because you could have a method that takes an "Employee" with the expectation that it's immutable. +1 I'm interested too now – Cruncher Jul 03 '15 at 21:42
  • @Cruncher - I read that using an interface might be undesirable as well. So what would be ideal, just creating 3 final classes, with no subclassing whatsoever? If I don't say final, I would have to worry about thread safety correct? –  Jul 03 '15 at 21:43
  • It's not just thread safety. The method could save the input as a field for use in later calls, only to find out that it's been changed! I don't know what the best solution is either. But I'm anxiously awaiting a good answer. – Cruncher Jul 03 '15 at 21:46
  • @Cruncher - My guess would be to create the 3 classes, but as final, however, that may not be the best approach. –  Jul 03 '15 at 21:48
  • Then you would have to overload all methods that can accept any employee... Seems like a poor solution to me. There has to be a better way – Cruncher Jul 03 '15 at 21:49
  • 2
    There's nothing wrong with using an interface. Yes, the interface wouldn't guarantee that all the implementations will be immutable but you can guarantee that all the implementations you provide are. Other implementations are not your problem. – biziclop Jul 03 '15 at 21:50
  • If you create an interface or a non-final (abstract or not abstract) class, then there is no way to prevent anyone from ever implementing the interface or subclassing the class with a class that is mutable. The most you can do is document that people shouldn't create mutable implementations or subclasses. – Jesper Jul 03 '15 at 21:51
  • @biziclop one of the selling points of object oriented programming, is the ability to "bullet proof" your code. That is, being able to provide some guarantee that you can be absolutely sure of. 5 years down the line he may create another employee implementation forgetting that it should be immutable – Cruncher Jul 03 '15 at 21:52
  • @Jesper That is true. Just as `String` is just one implementation of `CharSequence`, there are others that are mutable. – biziclop Jul 03 '15 at 21:52
  • 1
    @Cruncher You can bullet-proof YOUR code. But you can't bullet-proof other code in advance. There's no way to force immutability on all implementors of an interface, neither is there a real need to do so. – biziclop Jul 03 '15 at 21:53
  • @biziclop that's not comparable at all. There's nothing about a CharSequence that says it should be immutable. He wants all employees in general to be immutable, edit: I would consider this a rather compelling case to want to do so – Cruncher Jul 03 '15 at 21:54
  • @Cruncher In that case, the answer is that it isn't possible in Java. Though I still fail to see a valid reason why anyone would want that. – biziclop Jul 03 '15 at 21:55
  • @biziclop "Though I still fail to see a valid reason why anyone would want that" How is, "I want the argument passed to me guaranteed to be immutable" not a valid reason? You can talk about documentation all you want, but it's never the same as a guarantee by the compiler. By this logic we don't need private fields either. – Cruncher Jul 03 '15 at 21:58
  • @Cruncher To underline the impossibility of this requirement, consider how there's no automated way to tell whether a class itself is immutable in the first place. How could you enforce something on all implementations of an interface that the compiler can't even test in isolation on a single class? – biziclop Jul 03 '15 at 21:59
  • Oracle advices for achieving immutability: https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html Note however, there is no real immutabaility in java. It is possible to expose hidden field or constructor with reflection. It is also possible to generate dynamic proxy of final class that you could extend. – John Jul 03 '15 at 22:00
  • 3
    Two questions that come to mind: (1) is it logical to make a class representing an Employee immutable? One would assume that the state of an employee would change during his/her employment. (2) why should Accountant/ITDepartment/QualityAssurance inherit from Employee rather than be an attribute ("position") of it? – RealSkeptic Jul 03 '15 at 22:00
  • @biziclop for the record, I never said that interfaces should be able to enforce that its children be immutable. Just that I was looking for a good way to solve the problem as in the question. Surely such an interface would be the most obvious solution, but I never said that should be the answer. In fact, I said exactly in my first comment that an interface does not seem like the right solution. – Cruncher Jul 03 '15 at 22:02
  • @Cruncher Okay, let's put it this way: there is no way to enforce immutability of yet-to-be-written code. There are ways to encourage it, one of them being composition. – biziclop Jul 03 '15 at 22:06
  • @RealSkeptic - I agree that Employee would possibly be mutable, however, besides making methods protected/protected final, you have to worry about thread safety, how would you make a java class thread safe in this case? 2. You're correct it could be an attribute, however, I was doing some reading, and this question was bothering me, so I quickly thought of an example. –  Jul 03 '15 at 22:10
  • @Cruncher - Let me simplify my question, is it okay in some cases, for example this one, to create a class that is mutable? When I was reading, I seem to get the idea that creating mutable classes are a definite NO, but, in some cases, making a class that's mutable does make sense, correct? –  Jul 03 '15 at 22:22
  • @Nexusfactor It depends on so many things. Yes, in general you'd want as many of your classes to be immutable as possible. You can for example create an `Employee` class that has a `Department` field, which is then implemented by `IT`, `QA`, `Accounts` and so on. You can now make your `Employee` class immutable on the condition that the `Department` implementations are immutable too. There's no way to tell they will be but if you design your classes right, you can minimise the risk. – biziclop Jul 03 '15 at 22:23
  • @biziclop - Thanks for the reply, could you show me an example, how you would implement the above requirement in code? Just for my own understanding. You don't have to do all of them, Just Employee and QA –  Jul 03 '15 at 22:24

4 Answers4

7

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, ...);
  }
}
Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
  • Thanks for the code, just for my own understanding, could you do it with the employee/QA example? Secondly, In certain cases, such as the example in my post, could you create mutable classes and leave it? Provided that you add code for thread safety? –  Jul 03 '15 at 22:55
  • I was reading Effective Java, and it said to favor interfaces over abstract classes, now, if I were to use an interface, and in the interface only provide the getters, and certain methods, would that be okay as well? Provided in my classes the variables are private final. This way, the classes can't be changed after creation. –  Jul 03 '15 at 22:58
  • @Nexusfactor, no. There's no way to restrict classes that implement an interface except by restricting the visibility of the interface as a whole which leaves your clients without a way to mention a base type. – Mike Samuel Jul 04 '15 at 14:21
  • @Nexusfactor, please see my edits that show some skeleton code for some of your employee classes. – Mike Samuel Jul 04 '15 at 14:26
  • Just for my understanding, could you just show me how to defensive copy in this case name? Could I just make the getter final? –  Jul 04 '15 at 14:48
  • You would just say this.name = new name(parm1, parm2); correct? –  Jul 04 '15 at 14:55
  • Re defensive copying, if your input is mutable, then you can't rely on whoever called the constructor modifying it after the constructor finished, so you make a copy. – Mike Samuel Jul 05 '15 at 02:13
2

It's worth thinking why immutability is desirable - usually it's because you have data which you need to assume is not going to change. One approach to do this is to make a final Employee class with a non-final member containing the details specific to the subclasses:

public final class Employee {
    private final long employeeId;
    private final String firstName;
    private final String lastName;
    private final DepartmentalDetails details;

    public Employee(long employeeId, String firstName, String lastName,
            DepartmentalDetails details) {
        super();
        this.employeeId = employeeId;
        this.firstName = firstName;
        this.lastName = lastName;
        this.details = details;
    }
}

abstract class DepartmentalDetails {
}

final class AccountantDetails extends DepartmentalDetails {
    // Things specific to accountants
}

final class ITDetails extends DepartmentalDetails{
    // Things specific to IT
}

final class QualityAssuranceDetails extends DepartmentalDetails{
    // Things specific to QA
}

This isn't technically immutable (since an implementer could write a mutable implementation of DepartmentalDetails) but it does encapsulate the mutability, so it gives the same benefits while allowing some extensibility. (This is related to this is the concept of composition vs inheritance, although I don't believe this pattern is how that is normally used.)

One other possibility I think is worth considering - you could make the three subclasses as you suggest, make them all final, and stick a big comment on the abstract class saying all implementations should be immutable. It's not bulletproof, but on a small development project the complexity saving is likely to be worth the tiny risk.

hugh
  • 2,237
  • 1
  • 12
  • 25
  • Thanks for the code. I was also thinking, just make IT, QA, and Accountant, all final classes without the abstract employee, could that also work? –  Jul 03 '15 at 22:37
  • I'm sure you could make it work , but it'd mean a lot of switching code elsewhere to handle them all. It'd probably be very hard to add new employee types too, requiring changes to every part of the system that handles these objects. That suggests this is quite brittle and likely to cause you pain (and expense) in the long run. – hugh Jul 03 '15 at 22:48
  • In your opinion, in a situation such as this, you would create mutable classes, correct? How would this be done in a real world environment? Are mutable classes created and code for thread safety put in? –  Jul 03 '15 at 22:51
  • That said, if you want a low-tech approach, you could always make a big immutable Employee class containing an "employee type" and optional fields for each of the subtypes. (This works well if you want to persist your objects in SQL tables, and is one of the common patterns in JPA for storing subclasses). – hugh Jul 03 '15 at 22:51
  • I was reading Effective Java, and it said to favor interfaces over abstract classes, now, if I were to use an interface, and in the interface only provide the getters, and certain methods, would that be okay as well? Provided in my classes the variables are private final. This way, the classes can't be changed after creation. –  Jul 03 '15 at 23:00
  • Personally, I'd be happy to use comments - documentation is a valid way to get things done too! In real world scenarios I'd expect to rarely share the instances between threads, much more likely I'd be loading and saving them from persistent storage and worrying about how to handle different threads updating the same record in the database. – hugh Jul 03 '15 at 23:06
  • Interface-wise: I'd say *favour* interfaces, but they're not the right answer every time. An interface describes a behaviour, an abstract class describes a more general version of a thing - an employee sounds more like a general version of "IT employee" than a set of behaviours to me. Particularly, I'd expect employees to have implementation in common, which would sit well inside the abstract class. – hugh Jul 03 '15 at 23:20
  • In this particular case, could I make the employee class abstract, and use subclasses, as I stated I kind of get the idea that creating mutable object is undesirable, however, sometimes, it is justified correct? In this particular case, you could create abstract class/subclasses right? –  Jul 03 '15 at 23:24
  • I wouldn't say "undesirable" so much as idealistic. It's a good property, but make sure you know where the value is to your task, and don't die in a ditch just for the principle of it. – hugh Jul 03 '15 at 23:28
  • In this particular case, creating and using abstract/subclasses is alright, correct? –  Jul 03 '15 at 23:30
2

java.lang.String is special, very special - it's a basic type used everywhere. In particular, java security framework heavily depends on Strings, therefore it is critical that everybody sees the same content of a String, i.e. a String must be immutable (even under unsafe publication!) (Unfortunately a lot of people blindly apply those strict requirements of String to their own classes, often unjustified)

Even so, it's not really a big deal if String can be subclassed, as long as all methods in String are final, so that a subclass cannot mess with what a String is supposed to be like. If I'm accepting a String, and you give me a subclass of String, I don't care what you do in the subclass; as long as the content and the behavior of the String superclass is not tempered with.

Of course, being such a basic type, it's wise to mark String as final to avoid all confusions.


In your use case, you can just have an abstract Employee, and make sure all subclasses are implemented as immutable. Any Employee object, at runtime, must belong to a concrete subclass, which is immutable.

If, you cannot control who subclasses Employee, and you suspect that they are either morons that don't know what they are doing, or villains that intend to cause troubles, you can at least protect the part in Employee so that subclasses cannot mess it up. For example, the name of an Employee is immutable no matter what subclass does -

final String name;

protected Employee(String name) { this.name = name; }

public final String getName(){ return name; }  // final method!

However, such defensive design is often unjustified in most applications by most programmers. We don't have to be so paranoid. 99% of coding are cooperative. No big deal if someone really need to override something, let them. 99% of us are not writing some core APIs like String or Collection. A lot of Bloch's advices, unfortunately, are based on that kind of use cases. The advices are not really helpful to most programmers, especially new ones, churning out in-house apps.

ZhongYu
  • 19,446
  • 5
  • 33
  • 61
0

Java 17 and "Sealed Classes and Interfaces" feature

I think with Java 17 and "Sealed Classes and Interfaces" feature it became a bit more easier to follow immutability while using inheritance.

Now we can just restrict classes which may extend the parent class to ones that we are trusted to.

Let me show an example based on the suggested entities and the feature (pay attention on MutabilityBreaker which is not compiled due to the restriction of Employee class):

/**
 * Class that we would like to be immutable.
 * Pay attention on 'permits' section
 */
public sealed class Employee permits Accountant, ITWorker {

    private final String id;

    public Employee(String id) {
        this.id = id;
    }

}

/**
 * Class that we would like to inherit
 * from {@link Employee} and not break immutability
 */
public final class Accountant extends Employee {

    private final List<String> qualifications;

    // I'm very responsible. I don't want to break immutability, so I
    // carefully handle input collection
    public Accountant(String id, List<String> qualifications) {
        super(id);
        this.qualifications = List.copyOf(qualifications);
    }

}

/**
 * Another class that we would like to inherit
 * from {@link Employee} and not break immutability
 */
public final class ITWorker extends Employee {

    private final List<String> skills;

    // I'm very responsible too, I don't want to break
    // immutability too, so I carefully handle input collection too
    public ITWorker(String id, List<String> skills) {
        super(id);
        this.skills = List.copyOf(skills);
    }

}

/**
 * Class that was written to break immutability
 * <p>
 * COMPILE ERROR: 'MutabilityBreaker' is not allowed in the sealed hierarchy
 */
public class MutabilityBreaker extends Employee {

    private final List<String> mutableCollection;

    // I'm not responsible, I don't want to follow immutability
    public MutabilityBreaker(String id, List<String> mutableCollection) {
        super(id);
        this.mutableCollection = mutableCollection;
    }

}
HereAndBeyond
  • 794
  • 1
  • 8
  • 17