This article provides an overview of how to create an immutable class in Java programming.
An object is immutable when its state doesn’t change after it has been initialized. For example, String
is an immutable class and, once instantiated, the value of a String
object never changes. Learn more about why the String
class is immutable in Java.
Because an immutable object can’t be updated, programs need to create a new object for every change of state. However, immutable objects also have the following benefits:
Learn more about multi-threading in Java and browse the Java Multi-Threading Interview Questions.
To create an immutable class in Java, you need to follow these general principles:
final
so it can’t be extended.private
so that direct access is not allowed.final
so that a field’s value can be assigned only once.The following class is an example that illustrates the basics of immutability. The FinalClassExample
class defines the fields and provides the constructor method that uses deep copy to initialize the object. The code in the main
method of the FinalClassExample.java
file tests the immutability of the object.
Create a new file called FinalClassExample.java
and copy in the following code:
Compile and run the program:
Note: You might get the following message when you compile the file: Note: FinalClassExample.java uses unchecked or unsafe operations
because the getter method is using an unchecked cast from HashMap<String,String>
to Object
. You can ignore the compiler warning for the purposes of this example.
You get the following output:
OutputPerforming Deep Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second}
ce testMap after changing variable from getter methods: {1=first, 2=second}
The output shows that the HashMap values didn’t change because the constructor uses deep copy and the getter function returns a clone of the original object.
You can make changes to the FinalClassExample.java
file to show what happens when you use shallow copy instead of deep copy and return the object insetad of a copy. The object is no longer immutable and can be changed. Make the following changes to the example file (or copy and paste from the code example):
return (HashMap<String, String>) testMap.clone();
and add return testMap;
.The example file should now look like this:
Compile and run the program:
You get the following output:
OutputPerforming Shallow Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second, 3=third}
ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}
The output shows that the HashMap values got changed because the constructor method uses shallow copy there is a direct reference to the original object in the getter function.
You’ve learned some of the general principles to follow when you create immutable classes in Java, including the importance of deep copy. Continue your learning with more Java tutorials.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
… and here is a translation of the entire exercise into Scala, except without the wasteful copying and cloning, and with correct equals and hashCode methods: case class FinalClassExample(id: Int, name: String, testMap: Map[String,String])
- Ken
I didn’t understood what are you trying to point here?
- Pankaj
I belive that he was trying to point out that Scala lets you create immutable classes in more concise way. I’m new to Scala myself and already I love and often use these one liners. Less code less opportunities to make mistakes :)
- Paweł Chłopek
Thats a completely new thing to me… Got something new to learn now… will dig into this soon :)
- Pankaj
Shouldn’t it be “shallow copy” instead of “swallow copy” unless I am missing something?
- Shantanu Kumar
Thanks for pointing out the typo error. Corrected now to shallow copy.
- Pankaj
why don’t you just do this:
import static java.util.Collections.unmodifiableMap; public final class FinalClassExample { ... private final Map testMap; public FinalClassExample(int i, String n, Map m){ id = i; name = n; testMap = unmodifiableMap(new HashMap (m)); } public Map getTestMap() { return testMap; } ... }
- John
In this case, when we will get the testMap from getTestMap() function, we will not be allowed to modify it and it will throw exception. Also in that case again we are passing the reference and the values will change accordingly. Try executing this class:
package com.journeldev.java; import static java.util.Collections.unmodifiableMap; import java.util.HashMap; import java.util.Map; public final class FinalClassExample1 { private final Map testMap; public Map getTestMap() { return testMap; } public FinalClassExample1(Map hm) { this.testMap = unmodifiableMap(hm); } public static void main(String[] args) { HashMap h1 = new HashMap(); h1.put("1", "first"); h1.put("2", "second"); FinalClassExample1 ce = new FinalClassExample1(h1); System.out.println("ce testMap:" + ce.getTestMap()); h1.put("3", "third"); System.out.println("ce testMap after local variable change:"+ ce.getTestMap()); Map hmTest = ce.getTestMap(); hmTest.put("4", "new"); System.out.println("ce testMap after changing variable from accessor methods:"+ ce.getTestMap()); } }
Output will be: ce testMap:{2=second, 1=first} ce testMap after local variable change:{3=third, 2=second, 1=first} Exception in thread “main” java.lang.UnsupportedOperationException at java.util.Collections$UnmodifiableMap.put(Collections.java:1285) at com.journeldev.java.FinalClassExample1.main(FinalClassExample1.java:33)- Pankaj
Pankaj, You should not be allowed to modify a map you get from getTestMap. Objects coming from an immutable object, I would expect to also be immutable so you can not change the internals of the object after it is created. A mutable map may imply that the objects map is changed. Sure you can document that the object returned is new, but being defensive upfront seems better. Another alternative is to never return collections. Instead, define a forEach method that takes a function: public void forEach(Function fn) { for(Thing t : Iterable collection) { fn.apply(t); } }
- Steve
I am not sure why the map returned from getTestMap method should not be allowed to modify. Suppose the list contains some 1000 items and client wants to add 5 more to make their own immutable object instance, in this case its better to let them modify the returned object and do whatever they want on it. Usually, we have setter methods to change the state of object, so just by changing the returned object you should not think that the object state has been changed because the internal implementation is not known to us(as a client).
- Pankaj
you didn’t notice that I wrote:
this.testMap = modifiableMap(**new HashMap** (m)) `` so you couldn't change the inner map after construction. and I agree with Steve that the map returned by getTestMap() should not be modifiable as this would give the false impression that you're actually changing the immutable object. in that case I would prefer doing something like this: `FinalClassExample example = new FinalClassExample(...); Map newMap = new HashMap(example.getTestMap()); newMap.put("hello", "world");` ``
- John
Ok agreed that this will work but then its not generic. What if its some generic mutable class like Date or may be user defined class where you don’t have this feature. This example is intended to provide a generic way to create immutable classes. As for changing the object state, that is why we have setter methods and I still prefer it doing like that… but again it depends on your application requirements and design.
- Pankaj
In Groovy you can annotate the class as @Immutable and get /almost/ similar results to the scala example without all the boilerplate. IMHO Scala is better for it’s immutable support though. Also, don’t forget that Java Date, Dimension, and other JDK classes are not immutable as well, so you need to make defensive copies of those classes as well.
- Hamlet D’Arcy
Exactly, for all the mutable objects we need to return the defensive copy rather than same object reference. Have to dig into Scala now.
- Pankaj
Out of curiosity, why the requirement to have the class be marked as final so as not to be extended? What does disallowing subclasses actually provide in terms of allowing objects of this type to be immutable? Further, you don’t have to mark fields as private only just so long as you can guarantee that all constructor’s of the class properly initialize all of the fields. As a side note, you *can* have setters, but with the nuance that instead of changing an internal field, what the setter really does is specify a return type of the class the method is on, and then behind the scenes creates a new object using a constructor that accepts all internal fields, using the internally held state in for all params with the exception of the field represented by the setter called since you want the new object to have that field updated.
- whaley
If the class is not marked as final then its function can be overridden in the subclass either accidentally or intentionally. So its more related to keep the object secure. For this either all the getter methods can be made final or the class itself - this is again a design decision and depends on the requirement. Again if the fields wont be private then client application can override the value. Make the HashMap as public in the code and run the below code to see yourself.
FinalClassExample fce = new FinalClassExample(1,"", new HashMap()); System.out.println(fce.testMap); HashMap hm = fce.testMap; hm.put("1", "1"); System.out.println(fce.testMap);
Having a setter function will give the feeling that the actual object has been modified whereas internally creating a new object. Its better to client application know that its immutable (like String).- Pankaj
It’s super webpage, I was looking for something like this
- Łomża Zuhlke
Thanks mate, great details… – Anish Sneh
- Anish Sneh
Thanks for the kind words.
- Pankaj
Thanks, you know it and you know how to explain it too! I will definitely read more of your articles :)
- Mirey
Thanks for the detailed tutorial, well written and the flow goes exactely to showing up almost the need of every instruction in the code :) One side question, even if I know we are talking about Objects immutability,but what about the other instance variables you introucted in the
FinalClassExample
(id, name)? Is there any way to make them immutable?- Marwen
int and String both are already immutable, since there are no setter methods for them. For any other class variables, you should return a deep copy of the variable to avoid mutability.
- Pankaj
Should not be String name declared as a not final? Its not mutable anyway.
- Ramakant