DEV Community

Cover image for Make an Immutable Object - in Java

Make an Immutable Object - in Java

Gunnar Gissel on June 05, 2018

Originally published on www.gunnargissel.com Immutable objects are objects that don't change. You make them, then you can't change them. Inste...
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