I have defined the following classes trying to make "IamImmutable.class" immutable. But when I change hashmap values in TestingImmutability.class after initialising IamImmutable, the changes are applicable to the Hashmap. Hashmap would refer to the same object even if we instantiate it with new HashMap(old). I need to make Hashmap in the instance immutable. I have tried iterating and copying the values as well but that doesn't work. Can anyone suggest on how to proceed?
package string;
import java.util.HashMap;
import java.util.Map.Entry;
public final class IamImmutable {
private int i;
private String s;
private HashMap<String, String> h;
public IamImmutable(int i, String s, HashMap<String, String> h) {
this.i = i;
this.s = s;
this.h = new HashMap<String, String>();
for (Entry<String, String> entry: h.entrySet()) {
this.h.put((entry.getKey()), entry.getValue());
}
}
public int getI() {
return i;
}
public String getS() {
return s;
}
public HashMap<String, String> getH() {
return h;
}
}
And a test:
package string;
import java.util.HashMap;
import java.util.Map.Entry;
public class TestingImmutability {
public static void main(String[] args) {
int i = 6;
String s = "!am@John";
HashMap<String, String> h = new HashMap<String, String>();
h.put("Info1", "!am@John");
h.put("Inf02", "!amCrazy6");
IamImmutable imm = new IamImmutable(i, s, h);
h.put("Inf02", "!amCraxy7");
System.out.println(imm.getS() + imm.getI());
for (Entry<String, String> entry: h.entrySet())
System.out.println(entry.getKey() + " --- " + entry.getValue());
}
}
Expected output:
!am@ John6 Inf02---!amCrazy6 Info1---!am@ John
Actual output:
!am@ John6 Inf02---!amCraxy7 Info1---!am@ John
Your test is wrong, you're checking the contents of h
, the map that you passed to the constructor and later modified, instead of imm.getH()
. If you check the proper map
for (Entry<String, String> entry : imm.getH().entrySet())
System.out.println(entry.getKey() + " --- " + entry.getValue());
things look just fine:
!am@John6
Info1 --- !am@John
Inf02 --- !amCrazy6
So your IamImmutable
constructor is already fine, any later changes to the original map passed into the constructor won't affect the copy you made at construct time. You can also use the other HashMap constructor you mentioned, which is slightly more readable:
public IamImmutable(int i, String s, HashMap<String, String> h)
{
this.i = i;
this.s = s;
this.h = new HashMap<String, String>(h);
}
And that will work fine too.
A different problem is that getH()
passes a reference to the internal map out to the world, and if the world changes that reference, things will go wrong. An easy way to get around that is to apply the same copy trick that you already use in the constructor in getH()
as well:
public HashMap < String, String > getH() {
return new HashMap<String,String>(h);
}
Or to decorate the internal map before returning it:
public Map<String, String> getH() {
return Collections.unmodifiableMap(h);
}
Consider using the ImmutableCollections in the guava library instead. They've done the same job that this code is doing, but with a bit more thought to efficiency and ease of use. The full copies at construct time and to get the map are unwieldy and the standard underlying HashMap will do modification checks that are pointless if we know it isn't going to get modified anyways.
In order for your class to be immutable, getH()
must return a copy of the HashMap
. Otherwise, any caller of getH()
can modify the state of the HashMap
member of your IamImmutable
class, which means it's not immutable.
An alternative would be to replace getH()
with methods that access that internal HashMap
without exposing it. For example, you can have a method String[] keys()
that returns all the keys of the HashMap
and a method String get(String key)
that returns the value for a given key.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With