0

According to Java HashMap documentation, put method replaces the previously contained value (if any): https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html#put-K-V-

Associates the specified value with the specified key in this map. If the map previously contained a mapping for the key, the old value is replaced.

The documentation however does not say what happens to the (existing) key when a new value is stored. Does the existing key get replaced or not? Or is the result undefined?

Consider the following example:

public class HashMapTest
{

   private static class Key {
      private String value;
      private Boolean b;

      private Key(String value, Boolean b) {
         this.value = value;
         this.b = b;
      }

      @Override
      public int hashCode()
      {
         return value.hashCode();
      }

      @Override
      public boolean equals(Object obj)
      {
         if (obj instanceof Key)
         {
            return value.equals(((Key)obj).value);
         }
         return false;
      }

      @Override
      public String toString()
      {
         return "(" + value.toString() + "-" + b + ")";
      }
   }

   public static void main(String[] arg) {

      Key key1 = new Key("foo", true);
      Key key2 = new Key("foo", false);

      HashMap<Key, Object> map = new HashMap<Key, Object>();
      map.put(key1, 1L);

      System.out.println("Print content of original map:");
      for (Entry<Key, Object> entry : map.entrySet()) {
         System.out.println("> " + entry.getKey() + " -> " + entry.getValue());
      }

      map.put(key2, 2L);

      System.out.println();
      System.out.println("Print content of updated map:");
      for (Entry<Key, Object> entry : map.entrySet()) {
         System.out.println("> " + entry.getKey() + " -> " + entry.getValue());
      }
   }
}

When I execute the following code using Oracle jdk1.8.0_121, the following output is produced:

Print content of original map:
> (foo-true) -> 1

Print content of updated map:
> (foo-true) -> 2

Evidence says that (at least on my PC) the existing key does not get replaced.

Is this the expected/defined behaviour (where is it defined?) or is it just one among all the possible outcomes? Can I count on this behaviour to be consistent across all Java platforms/versions?

Edit: this question is not a duplicate of What happens when a duplicate key is put into a HashMap?. I am asking about the key (i.e. when you use multiple key instances that refer to the same logical key), not about the values.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Ciaccia
  • 382
  • 3
  • 14
  • "If the map previously contained a mapping for the key, the old value is replaced." so the value is replaced, `(foo-true) -> 1` old value is `1` and after replacement `(foo-true) -> 2` the new value is `2` – Anton Balaniuc Sep 28 '17 at 14:06
  • Possible duplicate of [What happens when a duplicate key is put into a HashMap?](https://stackoverflow.com/questions/1669885/what-happens-when-a-duplicate-key-is-put-into-a-hashmap) – Anton Balaniuc Sep 28 '17 at 14:08
  • 1
    Yeah, an HashMap uses HashCode, according to equals and hashcode contract, to check the uniqueness of the key. The point is that your 2 keys, from the HashMap POV, are exactly the same, because of your hashcode/equals implementation. So no, the key is not replaced, nice spot! – marco Sep 28 '17 at 14:12
  • Hi Anton Balaniuc, thank you for your answer. Nevertheless I asked about the key, not the value. I completely agree that the final value must be 2 (because the two keys `key1` and `key2` refer to the same logical key), but I was asking myself because the second key `key2` did not replace the first key `key1` in the HashMap. – Ciaccia Sep 29 '17 at 08:43

4 Answers4

0

From looking at the source, it doesn't get replaced, I'm not sure if it's guaranteed by the contract.

if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
}

It finds the existing mapping and replaces the value, nothing is done with the new key, they should be the same and immutable, so even if a different implementation can replace the key it shouldn't matter.

You can't count on this behavior but you should write your code in a way that it won't matter.

Oleg
  • 6,124
  • 2
  • 23
  • 40
  • Hi Oleg, thank you for your answer! Your answer reflects the outcome I got running the code, but does not say anything about the contract. I am interested to know if this is described somewhere and/or tested in Oracle's proprietary JCK/TCK test suite (which is used to determine if a Java implementation is standard or not) – Ciaccia Sep 29 '17 at 08:48
0

When a new pair is added, the map uses hasCode,equals to check if the key already present in the map. If the key already exists the old value is replaced with a new one. The key itself remains unmodified.

Map<Integer,String> map = new HashMap<>();
map.put(1,"two"); 
System.out.println(map); // {1=two}
map.put(1,"one"); 
System.out.println(map); // {1=one}
map.put(2,"two");
System.out.println(map); // {1=one, 2=two}

There is an issue with your equals and hashCode contract. ke1 and key2 are identical according to your implementation:

@Override
public boolean equals(Object obj)
{
    if (obj instanceof Key)
    {
       return value.equals(((Key)obj).value);
    }
    return false;
}

you need to compare Boolean b as well

Key other = (Key) obj;
return value.equals(other.value) && b.equals(other.b);

The same rule apples to hasCode

@Override
public int hashCode()
{
    return value.hashCode();
}

return value.hashCode() + b.hashCode();

with these changes key1 and key2 are different

System.out.println(key1.equals(key2));

and the output for your map will be

> (foo-true) -> 1
> (foo-false) -> 2
Anton Balaniuc
  • 10,889
  • 1
  • 35
  • 53
  • 1
    He did that on purpose. To check if the key gets replaced. – Oleg Sep 28 '17 at 14:16
  • @Oleg, but the key is not replaced, in case of the duplicated key, the value is replaced. – Anton Balaniuc Sep 28 '17 at 14:19
  • Yes, that's what I explained in my answer, I looked at the source to verify it, OP decided to test it with code. – Oleg Sep 28 '17 at 14:21
  • Hi Anton Balaniuc, in your answer you wrote "The key itself remains unmodified." (this is the outcome I observed and visible in the source code). Why do you say so? Where is this written/documented? – Ciaccia Sep 29 '17 at 08:50
  • @Ciaccia http://docs.oracle.com/javase/8/docs/api/java/util/Map.html#put-K-V- there is nothing about key modification here. If the key was modified in any way it would be reflected in the java doc – Anton Balaniuc Sep 29 '17 at 09:34
0

It is not replaced - neither it should. If you know how a HashMap works and what hashCode and equals is (or more precisely how they are used) - the decision of not touching the Key is obvious.

When you put the other Key/Entry in the map for the second time, that key is first look-up in the map - according to hashCode/equals, so according to the map IFF keys have the same hashCode and are equal according to equals they are the same. If so, why replace it? Especially since if it would have been replaced, that might rigger additional operations or at least additional code to not trigger anything else if keys are equal.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Hi Eugene, thank you for your answer. Where is this described/documented? Do you have access to some resources that describe or expect this behaviour? – Ciaccia Sep 29 '17 at 08:52
  • @Ciaccia you're asking about hashcode/equals? what precisly you mean – Eugene Sep 29 '17 at 08:58
  • @Ciaccia Asks about the same thing he originally asked. What's obvious is not important, if the contract doesn't guarantee that the key won't be replaced it can be replaced. – Oleg Sep 29 '17 at 09:42
  • @Oleg as the code is right it's not; you do make sense though – Eugene Sep 29 '17 at 10:04
0

Apparently the current HashSet implementation relies on this HashMap behaviour in order to be compliant to the HashSet documentation. With that i mean that when you add a new element in an HashSet the documentation says that if you try to add an element in an HasSet that already contains the element, the HashSet is not changed and so the element is not substituted, In the openjdk8 implementation the HashSet uses an HashMap keys to hold the values and in the HashSet.add method it calls the HashMap.put method to add the value, thus relying on the fact that the put method will not substitute the object

Although this still not a direct specification in the documentation and it's subject to variations in the JRE implementation, it probably provides a stronger assurance that this will probably not change in the future

Sambuccid
  • 166
  • 2
  • 4