DEV Community

Cover image for Make an Immutable Object - in Java
Gunnar Gissel
Gunnar Gissel

Posted on

Make an Immutable Object - in Java

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

  1. Making an Immutable Object
  2. Common gotchas
    1. Primitives
    2. Collections
    3. Arrays
    4. Objects
  3. How to Change an Immutable Object
    1. Where to put Builders?
  4. Handling Bad Data in Immutable Objects
  5. Wrapup

Gotchas!

a lonely tree on a rock

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
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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?

a woman having a good time in a shop

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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?

a very dirty car

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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

a colorful celebration

Immutable objects require some special care and handling, but their utility is worth it.

The main principles to keep in mind are:

  1. Clone arrays, Collections and Objects internal to your immutable object
  2. Use builders when you need a mutable object
  3. Use Optional to indicate nullable fields in your object's api
  4. Fail fast on bad data - Objects.requireNonNull can help

Go forth, and stop mutating

You've read this far, subscribe to my mailing list for more of the same. It's once a month, you can unsubscribe any time, and I promise not to spam you.

Top comments (17)

Collapse
 
test1975 profile image
Test

If you are using Java 10 or later, you can make defensive copies of collections using functions like List.copyOf and Map.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...

Collapse
 
monknomo profile image
Gunnar Gissel • Edited

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 😍

Collapse
 
mbtts profile image
mbtts • Edited

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 of add (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:

return (/* some condition */) ? list : Collections.emptyList();

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:

  1. Don't expose the list directly if it can be avoided.
  2. If it really must be exposed then always return a clone.

That makes the behaviour safer and more predictable.

Collapse
 
monknomo profile image
Gunnar Gissel

All good points and interesting things to consider

Collapse
 
moandip profile image
Sir Mo

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!

Collapse
 
monknomo profile image
Gunnar Gissel

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:

public static void doSomething(String arg1, Optional<String> arg2){
  Objects.requireNonNull(arg1);
  Objects.requireNonNull(arg2);
  if(arg2.isPresent()){
    //do whatever
  } else {
    //handle the absent value
  }
}

vs.

public static void doSomething(String arg1, String arg2){
  Objects.requireNonNull(arg1);
  if(null == arg2){
    //handle the absent value
  } else {
    //do whatever
  }
}   

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.

Collapse
 
monknomo profile image
Gunnar Gissel
Thread Thread
 
moandip profile image
Sir Mo

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 :)

Collapse
 
frothandjava profile image
Scot McSweeney-Roberts

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.

Collapse
 
monknomo profile image
Gunnar Gissel

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?

Collapse
 
frothandjava profile image
Scot McSweeney-Roberts

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.

Collapse
 
rapasoft profile image
Pavol Rajzak

There's also a nice library for creating Immutable objects - immutables.github.io/

Collapse
 
hackermsl profile image
Mateusz Sławomir Lach

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?

Collapse
 
hackermsl profile image
Mateusz Sławomir Lach • Edited

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?

Collapse
 
ashishmishraw profile image
ashish mishra • Edited

How to prevent immutability of objects during serialization and invocation through reflection APIs ?

Collapse
 
monknomo profile image
Gunnar Gissel • Edited

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

Collapse
 
ashishmishraw profile image
ashish mishra

Thanks. The blog is useful