Originally published on www.gunnargissel.com
Immutable objects are objects that don't change. You make them, then you can't change them. Instead, if you want to change an immutable object, you must clone it and change the clone while you are creating it.
A Java immutable object must have all its fields be internal, private final
fields. It must not implement any setters. It needs a constructor that takes a value for every single field.
Immutable objects come in handy in multi-threaded environments and in streams. It is great to rely on objects not changing mid-stream. Bugs caused by a thread changing another thread's object are often subtle and are very, very hard to track down. Immutable objects stop these whole class of problems in their tracks.
You don't have to take my word for it - see what experts around the web say.
Contents
- Making an Immutable Object
- Common gotchas
- How to Change an Immutable Object
- Handling Bad Data in Immutable Objects
- Wrapup
Gotchas!
Lists, arrays, maps, sets and other non-immutable objects can be surprising. A private final
object with no setter is fixed to the object it was initially assigned, but the values inside that object aren't fixed (unless the object is immutable).
That means you might have an ImmutableShoppingList myShoppingList = new ImmutableShoppingList(new String[] {"apples","anchovies","pasta"})
and expect that the shopping list will always have "apples", "anchovies" and "pasta".
Someone could call myShoppingList.getList()[0] = "candy bars";
and change your list to be "candy bars", "anchovies" and "pasta", which is unhealthy and clearly not what you want.
Primitives
Good news! Primitives are immutable, so you don't have to do anything special.
Collections
Good news! java.util.Collections provides a number of convenience methods that make converting a Collection to an UnmodifiableCollection a snap.
Check out:
Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableMap
Collections.unmodifiableNavigableMap
Collections.unmodifiableNavigableSet
Collections.unmodifiableSet
Collections.unmodifiableSortedMap
Collections.unmodifiableSortedSet
I suggest you store the fields as generic Collections (List, rather than ArrayList), and make the unmodifiable in the constructor, like so:
public class ImmutableShoppingList {
private final List<String> list;
public ImmutableShoppingList(List<String> list){
this.list = Collections.unmodifiableList(list);
}
public List<String> getList(){
return list;
}
}
This allows you to use IDE code generation to make the getters, which is nice, and contains all the input modifiers in one place, which is also nice.
Bad news! If you hang onto the reference to the collection when you create the collection, you can still modify it, even if you store it as an unmodifiable collection internally. Here's an example:
List<String> originalList = new ArrayList<>();
theList.add("apple");
ImmutableShoppingList blah = new ImmutableShoppingList(originalList);
originalList.add("candy bar");
The supposedly immutable shopping list started with an apple, and had a candy bar added to it after creation. What can we do about this?
Clone the list!
public class ImmutableShoppingList {
private final List<String> list;
public ImmutableShoppingList(List<String> list){
List<String> tmpListOfHolding = new ArrayList<>();
tmpListOfHolding.addAll(list);
this.list = Collections.unmodifiableList(tmpListOfHolding);
}
public String[] getList(){
return (String[]) list.toArray();
}
}
When we create the immutable object, we deep clone the collection, which severs the connection to the original reference. Now when we run the "sneak a candy bar in" example, "candy bar" gets added to originalList
, but not the ImmutableShoppingList
.
Arrays
Bad news! Java doesn't have any convenient methods to prevent arrays from being modified. Your best bet is to either hide the original array and always return a clone, or to not use arrays in the underlying implementation and instead convert a Collection object to an array.
I prefer to stick with Collections, but if you must have an array in your object's api, this is the approach I would take:
public class ImmutableShoppingList {
private final List<String> list;
public ImmutableShoppingList(String[] list){
this.list = Collections.unmodifiableList(Arrays.asList(list));
}
public String[] getList(){
return (String[]) list.toArray();
}
}
Objects
Object fields can be easy. If the sub-objects are also immutable, good news! You don't have to do anything special.
If the sub-objects are not immutable, they are a lot like a collection. You need to deep clone them, or the original reference can change your supposedly immutable data out from under your feet.
Often, you end up working with pre-existing mutable objects, either in your codebase, or in libraries. In this case, I like to create an immutable object wrapper class that extends the mutable class. I find a static getInstance(MutableObject obj)
method can be helpful, but a constructor ImmutableObject(MutableObject obj)
is also a useful thing to have.
What About When I Want To Change An Immutable Object?
It happens to everyone. You need an an object, but you don't know everything about the object. You can't quite commit to an immutable object.
In this case, I reach for the builder pattern.
The builder pattern creates a temporary object with the same fields as the desired object. It has getters and setters for all the fields. It also has a build()
method that creates the desired object
Imagine a small immutable object:
class ImmutableDog {
private final String name;
private final int weight
public ImmutableDog(String name, int weight){
this.name = name;
this.weight = weight;
}
public String getName(){
return this.name;
}
public int getWeight(){
return this.weight;
}
}
Here's what the builder would look like:
class ImmutableDogBuilder {
private String name;
private int weight;
public ImmutableDogBuilder(){}
public ImmutableDog build(){
return new ImmutableDog(name, weight);
}
public ImmutableDogBuilder setName(String name){
this.name = name;
return this;
}
public ImmutableDogBuilder setWeight(int weight){
this.weight = weight;
return this;
}
public String getName(){
return this.name;
}
public int getWeight(){
return this.weight;
}
}
Note the setters I really like this pattern of returning this
on each setters in builder classes, because it creates a very fluent api. You could use this ImmutableDogBuilder
like this:
ImmutableDogBuilder dogBuilder = new ImmutableDogBuilder().setName("Rover").setWeight(25);
You can imagine in classes with more fields that this compacts your code a lot.
Where To Put The Builder?
There are two schools of thought here.
On the one hand, you can create a separate class for the builder. This is easy, it is very conventional, and your IDE will probably group the classes together, because they probably have similar names.
On the other hand, you can embed the builder class in the immutable object class as a public static inner class.
I prefer to embed builder classes in immutable objects, because I view the builder as a helper for the immutable object, and not a standalone thing. It keeps them together and tightly coupled.
What About Immutable Objects With Bad Data?
It happens to everybody, especially if you accept input. Bad data!
Bad data is no good, but immutable bad data seems especially wrong - you can't even fix it!
I use two approaches to prevent bad data from getting turned into immutable objects.
My primary approach is a suite of business rules that test for sane, permissible data. The business rules look at builders, and if the builder passes, I deem it ok to create the immutable object.
My secondary approach is to embed a small amount of business logic in the immutable object. I don't allow required fields to be null, and any nullable field is an Optional
.
For example:
class ImmutableDog {
private final String name;
private final Optional<int> weight
public ImmutableDog(String name, Optional<int> weight){
Objects.requireNonNull(name);
this.name = name;
this.weight = weight;
}
public String getName(){
return this.name;
}
public Optional<int> getWeight(){
return this.weight;
}
}
This is an ImmutableDog
that requires a name, but does not require a weight. It's important to know what to call a dog, but it's not strictly necessary to know that Fluffy weighs 15 lbs.
Objects.requireNonNull
will immediately throw a NullPointerException
if a name is not provided. This prevents the creation of a nonsensical immutable object. It also allows users of the immutable object (such as streams or functions) to skip handling nulls. There are no nulls here.
Using Optional<int>
makes consumers of ImmutableDog
immediately aware that they may have to handle a null. Providing the Optional
api gives downstream users an easy, functional way of handling nulls.
Wrapup
Immutable objects require some special care and handling, but their utility is worth it.
The main principles to keep in mind are:
- Clone arrays, Collections and Objects internal to your immutable object
- Use builders when you need a mutable object
- Use Optional to indicate nullable fields in your object's api
- Fail fast on bad data - Objects.requireNonNull can help
Go forth, and stop mutating
Top comments (17)
If you are using Java 10 or later, you can make defensive copies of collections using functions like
List.copyOf
andMap.copyOf
.They make a copy which is inherently immutable. The beauty is, if the collection you are copying is already immutable, it will usually just return a reference to itself instead of making another (needless) copy.
I've been using the immutable collection classes in Google's Guava library to do the same thing for years.
docs.oracle.com/javase/10/docs/api...
google.github.io/guava/releases/sn...
Haven’t really played with 10 yet - that sounds really handy!
Love Guava, but I like to see how far I can get without including libraries. Guava is the library that I wish was built in 😍
This might be considered too defensive but I don't really like the immutable collections because there is no compiler safety for misuse - only runtime errors (same goes for singleton, empty collections). The
AbstractList
implementation ofadd
(i.e.throw new UnsupportedOperationException
...) is a good example of a refused bequest code smell.With hindsight the designers would probably have removed mutable methods from the
java.util.Collection
interface and introduced another interface/contract intended for use by concrete implementations concerned with mutability.This issue can be compounded when working with third party code if you do not have access to sources so (without decompiling) you are forced to rely on sensible design or good documentation.
In addition I have seen code like this:
Whilst it is a slight memory optimisation use the empty list (instead of instantiating and returning empty list) it also creates a nasty trap for the caller. Worst case this leads to a game of Charades where all lists are wrapped as new lists "just in case" ... leading to bigger allocation/memory overheads.
So my general approach is to either:
That makes the behaviour safer and more predictable.
All good points and interesting things to consider
If I remember correctly having optionals as fields is an antipattern or at least not really good practice. However I don't recall why.
What's the advantage in contrast to simply wrapping the object in the getter:
public Optional<int> getWeight() {
return Optional.ofNullable(weight);
}
?
Besides my question. Great article dude!
Thanks for reading!
What I've read is that people advise against taking Optional as an argument (this is a pretty fair example of the argument). I disagree with that, or at least I weight my considerations differently.
I think taking Optional as an argument clearly signals to the user of the class what the implementer of the class is expecting in terms of required fields. I feel that being clear to the users of the code is generally more important than being kind to the compiler or performance, but all of this is subject to the actual use case.
My unstated assumption is that everything is setup to fail immediately if a null is passed around. For example:
vs.
To me, that seems better than the normal null checking dance we Java programmers go through.
How does it compare to wrapping the object getter? I've tried it that way, and in terms of using the class it's fine. My biggest complaint is you can't just code gen the getters if you do that. I also taking Optional as an argument clarifies code enough to outweigh the disadvantages in most situations.
I guess I should also say that all this Optional stuff is in context to the immutable object. I'm a little on the fence with Optional in the builder, not sure how I feel one way or another.
Here's one from Brian Goetz that a lot of people find convicing
I still disagree, obviously not everyone at Oracle is on board, so....
¯\(ツ)/¯
Thanks for the explanation. In general I agree at least with your readability over performance thingy.
I'd still go for the getter way tho. However I agree that using an optional as argument can be ok. I would still prefer doing something like throwing an illegal argument exception because npes are kinda ugly. Also to remove that if else clutter you could wrap the arg2 in an optional and map or else.
However that is not really related to immutability. I liked the article :)
If you make the Builder a static inner class then you can give the class you're building a private constructor that takes an object of the Builder class. So you end up with
final Dog dog = ImmutableDog.builder().name("fido").weight(20).build();
Doing it this way enforces using the builder and I've found it helps with maintenance
You might have noticed that I don't bother with get/set prefixes on Builders - they're not Beans and I'm probably never going to read a value that I'm putting into the builder.
That's a nice way of doing it, and I prefer creating the immutable class from the builder.
I've found getters useful on builders for doing the final validation before creating the real immutable object - how do you usually handle validation?
A mix of in the not-a-setter method and the build method before calling the constructor (and maybe occasionally in the constructor, but not by preference). Depends on exactly why it's not valid.
There's also a nice library for creating Immutable objects - immutables.github.io/
Nice article! However I'm a bit confused. As far as I know it's impossible to use generic types with primitives in Java. Therefore following code won't compile:
private final Optional<int> weight;
instead you should type:
private final Optional<Integer> weight;
am I right? Or maybe it's written in newest version of Java and in Java 13 it's OK?
Nice article! However I'm a bit confused. As far as I know it's impossible to use generic types with primitives in Java. Therefore following code won't compile:
private final Optional<int> weight;
instead you should type:
private final Optional<Integer> weight;
am I right? Or maybe it's written in newest version of Java and in Java 13 it's OK?
How to prevent immutability of objects during serialization and invocation through reflection APIs ?
I wrote this with the mind that users would respect the api and try to break the contract at their own peril.
I'm trying to think of why you'd want your object to be immutable through serialization, and my guess is that you want an immutable singleton? Here's a blog post that covers a bit on dealing with serialization in that case: lingpipe-blog.com/2009/08/10/seria...
As far as reflection goes, you can't get away from it. It's built in. If someone really wants it, they could write some groovy code and ignore access modifiers altogether. Whatever happens after a downstream user brings in reflection to defeat immutability is kind of on them, imho
Thanks. The blog is useful