cruxion effux
cruxion effux

Reputation: 1054

Does Java HashMap require values to be immutable?

I am learning Java HashMap Class(Key,Value) and found why Key needs to be Immutable as if it is changed later after being added to hash it maybe in wrong bucket.

But what about Value ? Does it needs to be Immutable too ?

This came to me as I recently encountered a problem where I got weird behaviour(keys that were not in map returned true on containsKey()) when I was using AtomicInteger with addAndGet() method to change value.

Then I switched to putting new Integer() whenever I needed to change the value corresponding to a key and that resolved the problem.

EDIT

Well this was just to ask for Immutable but here's the code :

`

HashMap<Long , PreparedStatement> hash_map= new HashMap<Long, PreparedStatement>(); 
HashMap<Long , Integer> hash_map_count = new HashMap<Long , Integer>();

//some code here , lets say phone value comes to a function and function contains this code

 if(hash_map.containsKey( phone)) // present
        {
            ps_insert = (PreparedStatement)hash_map.get( phone);
            count_temp = hash_map_count.get( phone);

            if(count_temp == null)
            {
                System.out.println("************"+ phone);

            }

            count_temp.addAndGet(1); //no need to re-insert into map as value changed by +1 here.
        System.out.println("count : "+count_temp.get()); // 

    }
    else
    {
        ps_temp = conn.prepareStatement("INSERT into T_"+ phone+" VALUES (?,?,?,?,?,?,?)");
        hash_map.put( phone,ps_temp);
        count_temp = new AtomicInteger(1);
        hash_map_count.put( phone, count_temp);
        ps_insert = ps_temp;
        System.out.println("new phone :"+ phone+" count :"+count_temp.get());
    }

The problem was the ****** got printed with phone (i.e. I got null) in count_temp.How is that possible when I have inserted a AtomicInteger for phone Then I checked and found that phone was never inserted to hash map and still containsKey() returned true for it.

Now can anybody explain why that might have happened and why changing to Integer() and inserting new Integer(changed value) corrected it ?

Upvotes: 2

Views: 3027

Answers (2)

Ashok Prajapati
Ashok Prajapati

Reputation: 374

First, you are calling containesKey on other map and then getting value from another map. that's why you getting different result here.

if(hash_map.containsKey( phone))
   count_temp = hash_map_count.get( phone);
        if(count_temp == null)

Second, values doesn't need to be immutable in HashMap. But key need to be immutable. However map doesn't force any rule on it but if key is not immutable then you may see the unexpected behaviour in map. You can have mutable keys in map only if you make sure that the instance properties in key class which are used in hashcode() and equals(), are never changed after initialisation. For more details you can refer below blog. why-hashmap-key-should-be-immutable

Upvotes: 0

Davide Lorenzo MARINO
Davide Lorenzo MARINO

Reputation: 26926

Value of a Map can be modified.

Also a key can be modified. Key needs to follow the condition that equals() and hashCode() before and after a modification will give the same results (with same input value in case of equals()). Also if keys can be modified generally they are unmodifiable.

To better show why a key can be modifiable I add a little explanation on how HashMap works and how a ModifiableKey can be coded.

How works an HashMap:

Internally an HashMap uses an array of Entry where Entry is a tern (Key, Value, Entry).

table

0 --> {K, V, E} --> {K, V, null}
1 --> {k, V, null}
2
3
4 --> {K, V, null}
5

Here is the code of get()

 public V get(Object key) {
     if (key == null)
        return getForNullKey();
     int hash = hash(key.hashCode());
     for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
         Object k;
         if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
             return e.value;
     }
     return null;
 }

As you can see first is calculated the hashCode of the key. The hashCode is then used to find an index in the table. If the a Key exists in the map is possible to reach it looping through the linked list of Entry elements at that position. To check if that key exists the equals() method is used (inside the for loop).

How to build a modifiable key usable in a map

So suppose to have the following class that you like to use as key.

public class ModifiableKey {
    private String unmodifiablePart;
    private String modifiablePart;


    public ModifiableKey(String unmodifiablePart) {
        this.unmodifiablePart = unmodifiablePart;
    }

    public String getUnmodifiablePart() {
        return unmodifiablePart;
    }

    public boolean equals(Object obj) {
        ModifiableKey mk = (ModifiableKey) obj;
        return unmodifiablePart.equals(mk.getUnmodifiablePart());
    }

    public int hashCode() {
        return unmodifiablePart.hashCode();
    }

    public void setModifiablePart(String modifiablePart) {
        this.modifiablePart = modifiablePart;
    }

    public String getModifiablePart() {
        return modifiablePart;
    }
}

This class is modifiable. You can change the value of property modifiablePart as you like. But it can be used as well as key for a map infact equals and hashCode doesn't change their behaviour when the modifiablePart change values.

Upvotes: 5

Related Questions