An example of an immutable class with hashmap

I have defined the following classes trying to make "IamImmutable.class" immutable. But when I change the hashmap values ​​in TestingImmutability.class after IamImmutable is initialized, the changes are applied to the Hashmap. The Hashmap will refer to the same object even if we create it with a new HashMap (old). I need to make a Hashmap on an immutable instance. I've tried iterating and copying values, but it doesn't work. Can anyone suggest 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 the 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 Result:

  !am@ John6
Inf02---!amCrazy6
Info1---!am@ John

      

Actual output:

  !am@ John6
Inf02---!amCraxy7
Info1---!am@ John

      

+3


source to share


5 answers


The test is wrong, you are checking the content h

, the map you passed to the constructor and then modified instead imm.getH()

. If you check the correct card

for (Entry<String, String> entry : imm.getH().entrySet())
        System.out.println(entry.getKey() + " --- " + entry.getValue());

      

everything looks just fine:

!am@John6
Info1 --- !am@John
Inf02 --- !amCrazy6

      

So your constructor IamImmutable

is fine already, any subsequent changes to the original map passed to the constructor will not affect the copy you made during build. You can also use another HashMap constructor that you read a little about:

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 too.




Another problem is that it getH()

is passing an inner map link to the world, and if the world changes that link, things will go wrong. An easy way to get around this is to apply the same trick you already use in the constructor in getH()

:

public HashMap < String, String > getH() {
    return new HashMap<String,String>(h);
}

      

Or decorate the inner card before returning it:

public Map<String, String> getH() {
    return Collections.unmodifiableMap(h);
}

      


Consider the ImmutableCollections in the guava library instead . They did the same job as this code, but with much more concern for efficiency and ease of use. Full copies at build time and map retrieval are cumbersome, and the standard base HashMap will do modification checks, which are pointless if we know it won't be modified anyway.

+3


source


check

<K,V> Map<K,V> java.util.Collections.unmodifiableMap(Map<? extends K,? extends V> m)

      

but note that this creates a read-only view on the original map. so you can copy the input card.

http://docs.oracle.com/javase/6/docs/api/java/util/Collections.html?is-external=true#unmodifiableMap%28java.util.Map%29



this should work ...

public IamImmutable(int i,String s, Map<String,String> h) {
    this.i = i;
    this.s = s;

    Map<String,String> map = new HashMap<String,String>();
    map.putAll(h);
    this.h = Collections.unmodifiableMap(map);
}

      

you might also need to change the member declaration and get the methods for the base type Map

rather thanHashMap

+3


source


For your class to be immutable, it getH()

must return a copy HashMap

. Otherwise, any caller getH()

can change the state of HashMap

your class member IamImmutable

, which means it is not immutable.

The alternative would be to replace it getH()

with methods that access this inner HashMap

without exposing it. For example, you can have a method String[] keys()

that returns all keys HashMap

and a method String get(String key)

that returns a value for a given key.

+2


source


HashMap <MyKey, MyValue> unmodifiableMap = Collections.unmodifiableMap(modifiableMap); 

      

Use the above code for an immutable object.

0


source


In the first place, your variable declarations are wrong. It should be declared as final

. Why final

? So that no one can write methods for setting these variables. Also, it doesn't matter if you make a shallow or deep copy of your HashMap. A basic rule of thumb for sending a clone()

reference to a mutable object from your immutable class.

I have modified my class with minor changes. Here's the code:

package string;
import java.util.HashMap;
import java.util.Map.Entry;

public final class IamImmutable {
    private final int i;
    private final String s;
    private HashMap<String, String> h;

    public IamImmutable(int i, String s, HashMap<String, String> h) {
        this.i = i;
        this.s = s;

        // It doesn't matter whether you make deep or shallow copy
        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;
    }

    @SuppressWarnings("unchecked")
    public HashMap<String, String> getH() {
        // Here the main change
        return (HashMap<String, String>) h.clone();
    }
}

      

And your test class was also slightly wrong. I changed it with local and ancestor changes. Here's the test class:

package string;
import java.util.HashMap;

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);
        System.out.println("Original values : " + imm.getI() + " :: " + imm.getS() + " :: " + imm.getH());

        h.put("Inf02", "!amCraxy7");
        System.out.println("After local changes : " + imm.getI() + " :: " + imm.getS() + " :: " + imm.getH());

        HashMap<String, String> hmTest = imm.getH();
        hmTest.put("Inf02", "!amCraxy7");
        System.out.println("After ancestral changes : " + imm.getI() + " :: " + imm.getS() + " :: " + imm.getH());

    }
}

      

Hope this helps.

Greetings.

0


source







All Articles