Core Java

Objects Should Be Immutable

In object-oriented programming, an object is immutable if its state can’t be modified after it is created.

In Java, a good example of an immutable object is String. Once created, we can’t modify its state. We can request that it creates new strings, but its own state will never change.

However, there are not so many immutable classes in JDK. Take, for example, class Date. It is possible to modify its state using setTime().

I don’t know why the JDK designers decided to make these two very similar classes differently. However, I believe that the design of a mutable Date has a many flaws, while the immutable String is much more in the spirit of the object-oriented paradigm.

Moreover, I think that all classes should be immutable in a perfect object-oriented world. Unfortunately, sometimes, it is technically not possible due to limitations in JVM. Nevertheless, we should always aim for the best.

This is an incomplete list of arguments in favor of immutability:

  • immutable objects are simpler to construct, test, and use
  • truly immutable objects are always thread-safe
  • they help to avoid temporal coupling
  • their usage is side-effect free (no defensive copies)
  • identity mutability problem is avoided
  • they always have failure atomicity
  • they are much easier to cache
  • they prevent NULL references, which are bad

Let’s discuss the most important arguments one by one.

Thread Safety

The first and the most obvious argument is that immutable objects are thread-safe. This means that multiple threads can access the same object at the same time, without clashing with another thread.

If no object methods can modify its state, no matter how many of them and how often are being called parallel — they will work in their own memory space in stack.

Goetz et al. explained the advantages of immutable objects in more details in their very famous book Java Concurrency in Practice (highly recommended).

Avoiding Temporal Coupling

Here is an example of temporal coupling (the code makes two consecutive HTTP POST requests, where the second one contains HTTP body):

Request request = new Request("http://example.com");
request.method("POST");
String first = request.fetch();
request.body("text=hello");
String second = request.fetch();

This code works. However, you must remember that the first request should be configured before the second one may happen. If we decide to remove the first request from the script, we will remove the second and the third line, and won’t get any errors from the compiler:

Request request = new Request("http://example.com");
// request.method("POST");
// String first = request.fetch();
request.body("text=hello");
String second = request.fetch();

Now, the script is broken although it compiled without errors. This is what temporal coupling is about — there is always some hidden information in the code that a programmer has to remember. In this example, we have to remember that the configuration for the first request is also used for the second one.

We have to remember that the second request should always stay together and be executed after the first one.

If Request class were immutable, the first snippet wouldn’t work in the first place, and would have been rewritten like:

final Request request = new Request("");
String first = request.method("POST").fetch();
String second = request.method("POST").body("text=hello").fetch();

Now, these two requests are not coupled. We can safely remove the first one, and the second one will still work correctly. You may point out that there is a code duplication. Yes, we should get rid of it and re-write the code:

final Request request = new Request("");
final Request post = request.method("POST");
String first = post.fetch();
String second = post.body("text=hello").fetch();

See, refactoring didn’t break anything and we still don’t have temporal coupling. The first request can be removed safely from the code without affecting the second one.

I hope this example demonstrates that the code manipulating immutable objects is more readable and maintainable, b ecause it doesn’t have temporal coupling.

Avoiding Side Effects

Let’s try to use our Request class in a new method (now it is mutable):

public String post(Request request) {
  request.method("POST");
  return request.fetch();
}

Let’s try to make two requests — the first with GET method and the second with POST:

Request request = new Request("http://example.com");
request.method("GET");
String first = this.post(request);
String second = request.fetch();

Method post() has a “side effect” — it makes changes to the mutable object request. These changes are not really expected in this case. We expect it to make a POST request and return its body. We don’t want to read its documentation just to find out that behind the scene it also modifies the request we’re passing to it as an argument.

Needless to say, such side effects lead to bugs and maintainability issues. It would be much better to work with an immutable Request:

public String post(Request request) {
  return request.method("POST").fetch();
}

In this case, we may not have any side effects. Nobody can modify our request object, no matter where it is used and how deep through the call stack it is passed by method calls:

Request request = new Request("http://example.com").method("GET");
String first = this.post(request);
String second = request.fetch();

This code is perfectly safe and side effect free.

Avoiding Identity Mutability

Very often, we want objects to be identical if their internal states are the same. Date class is a good example:

Date first = new Date(1L);
Date second = new Date(1L);
assert first.equals(second); // true

There are two different objects; however, they are equal to each other because their encapsulated states are the same. This is made possible through their custom overloaded implementation of equals() and hashCode() methods.

The consequence of this convenient approach being used with mutable objects is that every time we modify object’s state it changes its identity:

Date first = new Date(1L);
Date second = new Date(1L);
first.setTime(2L);
assert first.equals(second); // false

This may look natural, until you start using your mutable objects as keys in maps:

Map<Date, String> map = new HashMap<>();
Date date = new Date();
map.put(date, "hello, world!");
date.setTime(12345L);
assert map.containsKey(date); // false

When modifying the state of date object, we’re not expecting it to change its identity. We’re not expecting to lose an entry in the map just because the state of its key is changed. However, this is exactly what is happening in the example above.

When we add an object to the map, its hashCode() returns one value. This value is used by HashMap to place the entry into the internal hash table. When we call containsKey() hash code of the object is different (because it is based on its internal state) and HashMap can’t find it in the internal hash table.

It is a very annoying and difficult to debug side effects of mutable objects. Immutable objects avoid it completely.

Failure Atomicity

Here is a simple example:

public class Stack {
  private int size;
  private String[] items;
  public void push(String item) {
    size++;
    if (size > items.length) {
      throw new RuntimeException("stack overflow");
    }
    items[size] = item;
  }
}

It is obvious that an object of class Stack will be left in a broken state if it throws a runtime exception on overflow. Its size property will be incremented, while items won’t get a new element.

Immutability prevents this problem. An object will never be left in a broken state because its state is modified only in its constructor. The constructor will either fail, rejecting object instantiation, or succeed, making a valid solid object, which never changes its encapsulated state.

For more on this subject, read Effective Java, 2nd Edition by Joshua Bloch.

Arguments Against Immutability

There are a number of arguments against immutability.

  1. “Immutability is not for enterprise systems”. Very often, I hear people say that immutability is a fancy feature, while absolutely impractical in real enterprise systems. As a counter-argument, I can only show some examples of real-life applications that contain only immutable Java objects: jcabi-http, jcabi-xml, jcabi-github, jcabi-s3, jcabi-dynamo, jcabi-simpledb The above are all Java libraries that work solely with immutable classes/objects. netbout.com and stateful.co are web applications that work solely with immutable objects.
  2. “It’s cheaper to update an existing object than create a new one”. Oracle thinks that “The impact of object creation is often overestimated and can be offset by some of the efficiencies associated with immutable objects. These include decreased overhead due to garbage collection, and the elimination of code needed to protect mutable objects from corruption.” I agree.

If you have some other arguments, please post them below and I’ll try to comment.

Reference: Objects Should Be Immutable from our JCG partner Yegor Bugayenko at the About Programming blog.

Yegor Bugayenko

Yegor Bugayenko is an Oracle certified Java architect, CEO of Zerocracy, author of Elegant Objects book series about object-oriented programing, lead architect and founder of Cactoos, Takes, Rultor and Jcabi, and a big fan of test automation.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

9 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jake Price
Jake Price
9 years ago

Try writing a game of moving a square on screen using arrow keys, with purely immutable objects.

Sure, its doable, but the game will be easier(and intuitive) if states were mutable e.g. encoding the top left corner of the square with a point

There are definitely uses for immutable objects but they depend on context. Making every object immutable is too restrictive to cover every use case. This shpuld be a use when needed rule, not a blanket law.

Jack
Jack
9 years ago

It depends on the type of an object, which is specified in a particular domain. If this is a value object, whithout the identity, then it surely should be immutable. When it is entity object, howerver, I would opt for mutble, as in the game example above.

Take for example some big graph object. Keeping it in pure functional, immutable form would require altering the entire graph object when changing a single node, for example.

Brice
Brice
9 years ago

Let’s take a real life example.
For example you have a Car class with 4 Wheels as attributes. How do you change a wheel on your car? Destroy the car and create a new one?

Yegor Bugayenko
9 years ago
Reply to  Brice

There are no “attributes” in OOP, only behavior (see http://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html). The car can’t change its own wheels, I guess. I would use a new entity “garage”, which fixes a wheel for me:

Car fixed = garage.replace(car, wheel);

How exactly the car will be fixed – I don’t care. All I care about is that I get a new “thing” back, that behaves like a car and its broken wheel is replaced. So, yes, I replace the entire car.

Makes sense?

Brice
Brice
9 years ago

That can make sense. But at some point we will need to know which wheel is on the car (to know the next time you’ll need to replace it for example) so :
boolean needFix = garage.checkWheels(car);

Which object is going to hold the link between the car and it wheels, how do you suggest it should be done?

Yegor Bugayenko
9 years ago
Reply to  Brice

See my answer below

Yegor Bugayenko
9 years ago

It’s up to the car. The car is responsible for checking its own wheels. And we never know whether it’s a car or an airplane or maybe a bicycle. Consider this scenario:

Car car = shop.buy(“Mercedez-Benz C63”);
if (car.hasFlatTire()) {
car = garage.replace(car, shop.buy(“Pirelli P Zero Rosso”));
}
Human jeff = car.driveTo(jeff, office);

Again, we don’t know what “garage” returns back. Maybe they change my car to a bicycle. Why do I care if it still can get Jeff to the office? :)

Pay attention that Jeff is also different, after he gets to the office.

Cris B
Cris B
8 years ago

In the Failure Atomicity example, the stack example is not implemented well enough to provide a proper example of why immutability is better. The boundary check should be performed before the size is incremented, so if a size failure occurs, the stack will not “be left in a broken state”. It amounts to a “straw man argument”. Also, if the stack were rewritten using an immutable implementation, would a new instance be created after each push or pop? Would the array also need to be treated as immutable? If so, then the array would need to be copied. What if… Read more »

FrankC
FrankC
8 years ago

Maybe is a dumb question, but what about persistence? I mean, if I make an instance persistent in a given relational database using an object id as primary key and change the entire object during a given transaction (having now a different object id), how could I process that? Should I delete the previous instance from the database and make the new one persistent? What about related instances?

Back to top button