By Pankaj Kumar and Andrea Anderson
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:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// fields of the FinalClassExample class
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Getter function for mutable objects
public HashMap<String, String> getTestMap() {
return (HashMap<String, String>) testMap.clone();
}
// Constructor method performing deep copy
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Deep Copy for Object initialization");
// "this" keyword refers to the current object
this.id=i;
this.name=n;
HashMap<String,String> tempMap=new HashMap<String,String>();
String key;
Iterator<String> it = hm.keySet().iterator();
while(it.hasNext()){
key=it.next();
tempMap.put(key, hm.get(key));
}
this.testMap=tempMap;
}
// Test the immutable class
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// print the ce values
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// change the local variable values
i=20;
s="modified";
h1.put("3", "third");
// print the values again
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Compile and run the program:
- javac FinalClassExample.java
- java FinalClassExample
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:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// fields of the FinalClassExample class
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Getter function for mutable objects
public HashMap<String, String> getTestMap() {
return testMap;
}
//Constructor method performing shallow copy
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Shallow Copy for Object initialization");
this.id=i;
this.name=n;
this.testMap=hm;
}
// Test the immutable class
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// print the ce values
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// change the local variable values
i=20;
s="modified";
h1.put("3", "third");
// print the values again
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Compile and run the program:
- javac FinalClassExample.java
- java FinalClassExample
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.
Java and Python Developer for 20+ years, Open Source Enthusiast, Founder of https://www.askpython.com/, https://www.linuxfordevices.com/, and JournalDev.com (acquired by DigitalOcean). Passionate about writing technical articles and sharing knowledge with others. Love Java, Python, Unix and related technologies. Follow my X @PankajWebDev
… 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
Shouldn’t it be “shallow copy” instead of “swallow copy” unless I am missing something?
- Shantanu Kumar
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 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
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
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
Should not be String name declared as a not final? Its not mutable anyway.
- Ramakant
Hi Pankaj, It was a great learning about creating Immutable objects.If you are performing step 5 and 6 then step 4 is not required I guess. You are not storing or returning original reference of HashMap, You are using clone concept for that, Hence as a result client application have no way to reassign new object to declared HaspMap. Please correct me If I am missing some thing Thanks& Regards Rais Alam
- Rais Alam
Can you please describe no 6. more deeply.I am not able to understand it.
- mahi
I can modify the object using ce.testMap.put(“10”, “ten”); Output: Performing Deep Copy for Object initialization true false ce id:10 ce name:original ce testMap:{2=second, 1=first} ce id after local variable change:10 ce name after local variable change:original ce testMap after local variable change:{2=second, 1=first, 10=ten} ce testMap after changing variable from accessor methods:{2=second, 1=first, 10=ten}
- Bijoy
private final String name; …Why this is final …String is already final …do we need to declare it again…???
- Ajaz
Replying to JournalDev
“String is already final.” - yes , it is , but that final is at class level which means you can’t extend the String class, while that "private final String " is for variable , so that we cant change the value of that object once initialized.
- Vinay
Thank you Pankaj. I’m a big fan of your writing skills. You cover every details and explain concepts in easy to understand language. Thanks again.
- Vijay Nandwana
Thanks for great article! Can we use testMap.clone() in the constructor instead of going through all of the map items with Iterator?
- Bektur Toktosunov
Hi Pankaj I didn’t Understood the System.out.println(h1 == ce.getTestMap()) answer is False. Can you Please explain why it is false.
- WAA
hi pankaj, i love to read your blog. here i found a hack at main() using a reflection how to prevent it. Class mc = ce.getClass(); Field fs = mc.getDeclaredField(“name”); fs.setAccessible(true); fs.set(ce, “hacked”); System.out.println(“ce name after reflection hack:”+ce.getName());
- tabish
When you make the field final, Why making the variable private is mandate?
- Rahul
I it necessary to have variable as final?? We can achive it without it also, there is no statement to change varable.
- Pratyush
Hi setter method for map provides shallow copy only though clone method so that we can change the value later. How come you can say its immutable ?
- Anbu
Correct output is:: Performing Shallow Copy for Object initialization true false ce id:10 ce name:original ce testMap:{2=second, 1=first} ce id after local variable change:10 ce name after local variable change:original ce testMap after local variable change:{3=third, 2=second, 1=first} ce testMap after changing variable from accessor methods:{3=third, 2=second, 1=f irst}
- Rushabh
I am not clear on Point 5 initialization swallow or Deep are comparison not initialization. swallow comparison is done by == and Deep comparison by equal / equalignorecase Did you mean initialization should be done at constructor with safer way without exposing identity of the fields?
- kuldeep patil
I also wrote an article with a complete different view and you may have a look, https://www.codedjava.com/2017/09/immutable-objects-in-java\_50.html Thanks,
- Sameera
What if we remove the final access modifier from class as we are independently handling all the fields or methods of this class. And if some one extend this class then they doesn’t impact this class instance. Please suggest.
- Vaibhav Jetly
Your getter method for HashMap can be in that way public HashMap getTestMap() { //return testMap; //HashMap tempMap=new HashMap(); return new HashMap(testMap); }
- TEJENDRA PRATAP SINGH
correct output is: Performing Shallow Copy for Object initialization true false 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 accessor methods:{1=first, 2=second, 3=third}
- Hagos haile
Hi Pankaj, What will happen if we do not declare class as final, since member variables are privare so can not be extendable. I am here trying to understand why we need final for class and its member variable. Is there any way state of a class can be modified if we don’t declare class and its variables final? Thanks, Prahlad.
- Prahlad Kumar
Hi Pankaj, Thanks for writing such an informative article. I would like to know what if my all member variables are immutable like all Strings or Wrappers?, Do I still need to follow above steps? Thanks,
- Jitendra Singh
Great Article… very helpful in cracking interviews … Thank you so much !!
- Omkar J
Article is very nice and easy to understand. I have gone through the comments and got deeper understanding of this concept. Requesting everyone else to go through the discussions done in comments then you will get more understanding about this topic. Thanks to Pankaj.
- Vijay Kumar
This is how we create an immutable class. But can you please describe how the immutable object is created? or is that happens in java or not??
- u.mang
Not required map iteration in constructor and return statement clone which you used, may be any other reason please provide that. class Student { private final int sid; private final String sname; private final Map map; public Student(int sid, String sname, Map map) { super(); this.sid = sid; this.sname = sname; this.map = new HashMap(map); //this.list=list; } public int getSid() { return sid; } public String getSname() { return sname; } public Map getList() { return map; } @Override public String toString() { return “Student [sid=” + sid + “, sname=” + sname + “]”; } }
- Parvise
Hi, please post your Java code of connection pooling in java without using Spring and hibernate.
- Deepak
It was worth a read about immutable class in Java! Really learned a lot form this article. I can imagine the effort you put into this & especially appreciate you sharing it. keep up the good work.
- online java training
Hi, First i want to thank you for sharing your knowledge with us. here i have one question with this if i am getting HashMap reference through getter method then i can avoid changing Immutable class value by sending deep copy. But what if user accessed it directly? i mean Map local = ce.testMap; local.put(“local” , “local”); in this case ce.testMap will have the above added object as well. How can we avoid this? thanks!
- Pandidurai
correct output is: Performing Shallow Copy for Object initialization true false 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 accessor methods:{1=first, 2=second, 3=third}
- Hagos haile
Hi Pankaj, What will happen if we do not declare class as final, since member variables are privare so can not be extendable. I am here trying to understand why we need final for class and its member variable. Is there any way state of a class can be modified if we don’t declare class and its variables final? Thanks, Prahlad.
- Prahlad Kumar
Hi Pankaj, Thanks for writing such an informative article. I would like to know what if my all member variables are immutable like all Strings or Wrappers?, Do I still need to follow above steps? Thanks,
- Jitendra Singh
Great Article… very helpful in cracking interviews … Thank you so much !!
- Omkar J
Article is very nice and easy to understand. I have gone through the comments and got deeper understanding of this concept. Requesting everyone else to go through the discussions done in comments then you will get more understanding about this topic. Thanks to Pankaj.
- Vijay Kumar
This is how we create an immutable class. But can you please describe how the immutable object is created? or is that happens in java or not??
- u.mang
Not required map iteration in constructor and return statement clone which you used, may be any other reason please provide that. class Student { private final int sid; private final String sname; private final Map map; public Student(int sid, String sname, Map map) { super(); this.sid = sid; this.sname = sname; this.map = new HashMap(map); //this.list=list; } public int getSid() { return sid; } public String getSname() { return sname; } public Map getList() { return map; } @Override public String toString() { return “Student [sid=” + sid + “, sname=” + sname + “]”; } }
- Parvise
Hi, please post your Java code of connection pooling in java without using Spring and hibernate.
- Deepak
It was worth a read about immutable class in Java! Really learned a lot form this article. I can imagine the effort you put into this & especially appreciate you sharing it. keep up the good work.
- online java training
Hi, First i want to thank you for sharing your knowledge with us. here i have one question with this if i am getting HashMap reference through getter method then i can avoid changing Immutable class value by sending deep copy. But what if user accessed it directly? i mean Map local = ce.testMap; local.put(“local” , “local”); in this case ce.testMap will have the above added object as well. How can we avoid this? thanks!
- Pandidurai
Even within this constructor if you assign cloned version of input “hm” to testMap, it should still work, no need of deep copy. public FinalClassExample(int i, String n, HashMap hm){ System.out.println(“Performing Shallow Copy for Object initialization”); this.id=i; this.name=n; this.testMap=hm.clone(); }
- Ravi Beli
But when we trying to change a String object a new object would be created with those changes. Is that possible here ?. Please explain
- Sreerag
One of the best example and explanation for “creating immutable class”. Loved it. Thanks !!
- Mukund Padale
4. Make all mutable fields final so that it’s value can be assigned only once. I don’t think this is necessary if are declaring the fields private. mutable fields marked as final can still be mutated, only the reference can not be changed.
- Manohar Bhat
What about Date variable we can still change the date by writing d.getTime(); How can we avoid that?
- jogi
Hi Pankaj, I have been following your site for quite some time & it has been helping me a lot… Thank you very much for that. Just one suggestion, the “scrollable” code blocks are not very user-friendly to use. It would be great if we can see the entire code at once - even though it increases the height of the page, just my personal experience…
- Shobhit Mittal
hi Pankaj, great work !!! what is the need of getter method HashMap clone as we are constructing deep clone object? thanks Aravind Sundarraj
- Aravind Sundarraj
Hi, its Explained very nicely, I have one question like i have 2 classes as below. class Separtment{ int id, String name; } Class Employee{ Department Dept, int id, String name; } Here i want to make Employee as immutable, but here Department is a child object, so here how come i make Employee class is immutable. Also One constraint like the Department object which I won’t own(I am not allowed to change anything in the Department object).
- Akhil Kumar Patro
HI Pankaj, I tried your code and analysed that by only returning the map.clone() object will work here no need to make deep copy in constructor.
- Lokesh Kumar
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.