Hashmap : How map works when we put same key [duplicate] - java

This question already has answers here:
What happens when a duplicate key is put into a HashMap?
(12 answers)
Closed 3 years ago.
Map<String, String> map = new HashMap<String, String>();
I am aware about the internal working of map. if the hashcode for two keys are same collision occurs and it will store in linkedlist. but what if put that key again.
is there any check on that ? or it will simply replace the value without checking ?
to elaborate more
map.put("sparsh", "sparsh");
map.put("sparsh", "1");
it will replace value "sparsh" to "1"
map.put("sparsh", "1");
map.put("sparsh", "1");
what will happen in above case, any replacement ?
and some different context from above question
System.out.println(map.put("sparsh", "sparsh"));
System.out.println(map.put("sparsh", "1"));
it is giving me first "null" and then "sparsh"(key). what is the reason behind that, if anyone has clarity about that.

The contract of put is:
when the key is unknown, you add a new key/value pair,
when the key is known, you update (overwrite) the value of an existing key/value pair.
In your case, you can't observe the difference between "putting in" the new value, or keeping the old one. Two strings with two values are always equal, thus it doesn't really matter anyway.
But then: you can easily construct an example (using a class with two fields) where two objects are equal, but yet have different values (because only one of the two fields is used within the equals() implementation) .
And therefore the new value must go into the map, even if it is equal to the one already present.

From documentation of Map::put :
V put(K key, V value)
Returns: the previous value associated with key, or null if there was
no mapping for key. (A null return can also indicate that the map
previously associated null with key, if the implementation supports
null values.)
What does this mean
When you add an entry to the map :
if the entry with that key exist then it will return the last value,
else it will return null as you get in your example.
Here is an example with multiple puts :
System.out.println(map.put("key", "v1")); // null
System.out.println(map.put("key", "v2")); // v1
System.out.println(map.put("key", "v3")); // v2
System.out.println(map.put("key2", "v4")); // null

java.util.Map.put(key, value) returns the previous value associated with the same key.
So initially, there is no value associated with the key, so the first map.put("sparsh", "sparsh") returns null.
Then, since you have key: sparsh, value: sparsh in the map, calling map.put("sparsh", "1") will return the previous value, as in "sparsh"

Related

Can I declare a HashMap<key> instead of a HashMap<key,value>?

I'm trying to insert keys into a hashmap but I don't really need values inserted. I could but they wouldn't be used. I know hashmaps can accept null keys and values but only one null key,value pair. I could map.put(key,null) with the values being null but that method seems inefficient. My intention is that I'm going to use the map.containsKey(key) method to determine if a key exists in a hashmap which is why I don't need a value.
With that being said, is there a way to declare HashMap<key> instead of HashMap<key,arbitraryValue> so I won't have to add unnecessary null values? Sorry if this may be a dumb question.
As Jacob G. said, you should use a Set<E>.
A set is just a collection that does not contain any duplicates. The implementing class HashSet<E> actually uses a HashMap<K,V> under the hood, as mentioned by Ole V.V., but using a Set<E> in your code is the better approach IMO, because your problem does not require the values.
Additionally, there is a problem with using a HashMap<K,V> where all your values are null is in the get(K key) method. This method will return null if the requested key does not have an associated value. So how do you know if your call to get returned a valid or invalid null? i.e.
Map<Integer, Object> map = new HashMap<>();
// add entry 0 ->
map.put(0, null);
Object get1 = map.get(0); // returns null, so 0 must be a key in our map!
Object get2 = map.get(1); // also returns null, so is 1 a key too? No!
So, for your specific problem, I would try something like this!
Set<MyKey> set = new HashSet<>(); // or any implementing class
...
MyKey someKey = ...
// Check if your key set doesn't have some key, if so add it to the key set
if (!set.contains(someKey)) {
set.add(someKey);
}

Is it possible to add a key without a value in hashtable Java?

I'm trying to work with Java's Junit Test. My goal is to create a hashtable as
Hashtable< String , Hashtable<String,String> > student =
new Hashtable<String, Hashtable<String, String>>();
Next, I want to add at first only the key. After that, when I have the value then add it to the key in hashtable.
Example
student.put("student1",null) ;
I tried to work with null, but during test process I get a
java.lang.NullPointerException
Is it not possible? Any ideas to how adding only the key without value?
Is it possible to add a key without a value in hashtable Java?
Short answer: No
Why?
Because it will throw and Exception if
* #exception NullPointerException if the key or value is
* <code>null</code>
And in the same source code has a validation
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
...
You can use a Map Instead.
HashTable is an older implementation and HashMap came as an advanced version with more capabilities. You can't call .equals() or .hashCode() on it as null isn't an object.
HashMap is a better replacement for single threaded applications or any time synchronization is not a requirement, because of the performance impact synchronization introduces. If you need a Threadsafe option you can also use ConcurrentHashMap
The documentation says that you can't:
Maps the specified key to the specified value in this hashtable.
Neither the key nor the value can be null.
The best that you can do, if you need the key, is to use an empty HashTable as the value:
student.put("student1", new Hashtable<>());
In most use cases you would not care about putting null into a HashMap. You can check which keys have a value assigned via the keySet() and treat everything else as null (since it was never assigned).
However, your question to me indicates that you need to distinguish three states: Key-Value pair, key that were never assigned a value, and keys that were explicitly assigned a null value.
Since the default HashMap does not support this behavior, you could implement your own class that implements the Map interface. It basically only wraps a HashMap, with a few important exceptions:
When you assign a value of null to a key, you put the key into a Set of "NullKeys" instead of into the HashMap.
When you retrieve a key, check whether it is in the "NullKey" Set. If yes, return null, otherwise look into the HashMap.
There are some special cases (overwriting an existing value with null etc.) but this would be the basic strategy.
(I am not sure whether this is a useful class to have other than in very specialized scenarios but it would meet your requirements.)

Having HashMap inside a HashMap - call by reference or duplicate keys?

I was just wondering, when having a HashMap<HashMap<Integer, Integer>, String> and I add as key a new HashMap, does it get treated as a duplicate key or we have a call by reference and the value is not looked at at all?
Thanks :)
#Stephen has given a very good conceptual explanation.
So Just in brief:
When we are "putting" something in a HashMap it is internally stored in a "table" which is an array of "Entry"
Now at which position (bucketIndex) of that table it will be stored will be decided based on the "hashcode" of the key to be stored.Before storing it JVM will check if in that bucketIndex any "Entry" is present.This case is possible when two "keys" would have same hashcode.Now if there is an Entry JVM will further check if the "key" itself is identical(to know identical JVM will call "equals" on that key) to "key" already present.If yes it will consider it as a duplicate key and update its respective value in "Entry". If no it will just add another entry at the same bucket Index.
At time of "getting" the value from the map by sending a "key" ,similar process will run.First "hashcode" of the key would be extracted and depending on this the position (bucketIndex) of the table to be looked would be determined. Now if there is no content in that index "null" would be returned.Otherwise...
JVM will go to that index and there is a possibility that more than one "Entry" can be there because more than one object can have same hashcode. Now JVM will call "equals" method on that key to check if the Key present in the table is same as the inputKey sent for retrieval of value. if "equals" returns true then we will get the desired value.
So in general a key will be considered as a duplicate key if and only if hashcode() for both will return same value and equals() will return true.
Now coming to your question "I add as key a new HashMap, does it get treated as a duplicate key ", the answer is yes if and only if your new HashMap has exact same Entry i.e key,value pairs as that of an existing HashMap key.
Because :
if you will have a look at hashcode() implemenatation of HashMap then you will see its hashcode is calculated based on the "Key" and "value".So if 2 hashmaps will have same set,they will have the same hashcode().
the equals() of HashMap checks if Entry are identical.So again if two Hashmaps will have same set they are equal.
Now see the below code demonstrating this concept :
public static void main(String[] args) {
// TODO Auto-generated method stub
HashMap<Integer,Integer> keyMap=new HashMap<Integer, Integer>();
keyMap.put(2, 1020);
keyMap.put(3, 1352);
keyMap.put(23,1256);
System.out.println("hashcode keymap1:"+keyMap.hashCode());
HashMap<Integer,Integer> keyMap2=new HashMap<Integer, Integer>();
keyMap2.put(1, 100);
keyMap2.put(4, 152);
keyMap2.put(43,156);
System.out.println("hashcode keymap2:"+keyMap2.hashCode());
HashMap<HashMap<Integer,Integer>,String> mainMap=new HashMap<HashMap<Integer,Integer>,String>();
mainMap.put(keyMap, "1st value");
mainMap.put(keyMap2, "2ndvalue");
System.out.println(mainMap);
HashMap<Integer,Integer> keyMap3=new HashMap<Integer, Integer>();
keyMap3.put(23,1256);
keyMap3.put(3, 1352);
keyMap3.put(2, 1020);
System.out.println("hashcode keymap3:"+keyMap3.hashCode());
mainMap.put(keyMap3, "3rd value");
System.out.println(mainMap);
if(mainMap.containsKey(keyMap3))
System.out.println(" value retrieved is :"+mainMap.get(keyMap3));
else
System.out.println("key not found");
}
Here you can observe keymap and keymap3 are having same set of key,value and same hashcode. So both are here duplicate key, hence value of keymap is updated by value of keymap3.
The contract for the HashMap.equals(Object) method is:
"Compares the specified object with this map for equality. Returns true if the given object is also a map and the two maps represent the same mappings. More formally, two maps m1 and m2 represent the same mappings if m1.entrySet().equals(m2.entrySet())."
Now the standard behavior of a Map is to treat keys as the same if equals(Object) says they are equal.
So the answer to your question is that if you have
HashMap<Integer, Integer> k1 = // some map
HashMap<Integer, Integer> k2 = // another map
HashMap<HashMap<Integer, Integer>, String> map = // some
then using k1 and k2 as keys in map would give you one entry if k1.equals(k2) and two entries otherwise.
And given that k1 and k2 are maps, we determine if they are equal by comparing their respective sets of map entries.
This has two obvious problems:
If you change k1 or k2 while they are keys for entries in map, then you break a fundamental invariant for map. When that happens you will find that operations on map give incorrect results; e.g. map.get(k1) will give the wrong answer.
Whenever you do an operation involving a lookup on map, you will call HashMap.hashCode() for a key object. Calculating the hash code for the key entails calculating the hashcode for each and every key and value in the map HashMap<Integer, Integer>. That is expensive, especially since this HashMap.hashCode() does not (cannot) cache anything.
In short, using a HashMap as a key for another HashMap is a bad idea.
So, to answer your question:
I was just wondering, when having a HashMap<HashMap<Integer, Integer>, String> and I add as key a new HashMap, does it get treated as a duplicate key or we have a call by referencea and the value is not looked at at all?
It will not be a duplicate keyb unless the respective keys are maps with the same set of entries. This is what HashMap.equals(Object) tests.
It is not compared as a reference; i.e. it is not compared with == semantics. The HashMap.equals(Object) method is used for comparisons.
a - Note that "call by reference" terminology is not applicable to this situation. Call by reference / call by value is about how parameters are passed when a method is called.
b - .... provided that you don't violate the invariant.

Does adding a duplicate value to a HashSet/HashMap replace the previous value

Please consider the below piece of code:
HashSet hs = new HashSet();
hs.add("hi"); -- (1)
hs.add("hi"); -- (2)
hs.size() will give 1 as HashSet doesn't allow duplicates so only one element will be stored.
I want to know if we add the duplicate element, then does it replace the previous element or it simply doesn't add it?
Also, what will happen usingHashMap for the same case?
In the case of HashMap, it replaces the old value with the new one.
In the case of HashSet, the item isn't inserted.
The first thing you need to know is that HashSet acts like a Set, which means you add your object directly to the HashSet and it cannot contain duplicates. You just add your value directly in HashSet.
However, HashMap is a Map type. That means every time you add an entry, you add a key-value pair.
In HashMap you can have duplicate values, but not duplicate keys. In HashMap the new entry will replace the old one. The most recent entry will be in the HashMap.
Understanding Link between HashMap and HashSet:
Remember, HashMap can not have duplicate keys. Behind the scene HashSet uses a HashMap.
When you attempt to add any object into a HashSet, this entry is actually stored as a key in the HashMap - the same HashMap that is used behind the scene of HashSet. Since this underlying HashMap needs a key-value pair, a dummy value is generated for us.
Now when you try to insert another duplicate object into the same HashSet, it will again attempt to be insert it as a key in the HashMap lying underneath. However, HashMap does not support duplicates. Hence, HashSet will still result in having only one value of that type. As a side note, for every duplicate key, since the value generated for our entry in HashSet is some random/dummy value, the key is not replaced at all. it will be ignored as removing the key and adding back the same key (the dummy value is the same) would not make any sense at all.
Summary:
HashMap allows duplicate values, but not keys.
HashSet cannot contains duplicates.
To play with whether the addition of an object is successfully completed or not, you can check the boolean value returned when you call .add() and see if it returns true or false. If it returned true, it was inserted.
The docs are pretty clear on this: HashSet.add doesn't replace:
Adds the specified element to this set if it is not already present. More formally, adds the specified element e to this set if this set contains no element e2 such that (e==null ? e2==null : e.equals(e2)). If this set already contains the element, the call leaves the set unchanged and returns false.
But HashMap.put will replace:
If the map previously contained a mapping for the key, the old value is replaced.
It the case of HashSet, it does NOT replace it.
From the docs:
http://docs.oracle.com/javase/6/docs/api/java/util/HashSet.html#add(E)
"Adds the specified element to this set if it is not already present. More formally, adds the specified element e to this set if this set contains no element e2 such that (e==null ? e2==null : e.equals(e2)). If this set already contains the element, the call leaves the set unchanged and returns false."
Correct me if I'm wrong but what you're getting at is that with strings, "Hi" == "Hi" doesn't always come out true (because they're not necessarily the same object).
The reason you're getting an answer of 1 though is because the JVM will reuse strings objects where possible. In this case the JVM is reusing the string object, and thus overwriting the item in the Hashmap/Hashset.
But you aren't guaranteed this behavior (because it could be a different string object that has the same value "Hi"). The behavior you see is just because of the JVM's optimization.
HashMap basically contains Entry which subsequently contains Key(Object) and Value(Object).Internally HashSet are HashMap and HashMap do replace values as some of you already pointed..but does it really replaces the keys???No ..and that is the trick here. HashMap keeps its value as key in the underlying HashMap and value is just a dummy object.So if u try to reinsert same Value in HashMap(Key in underlying Map).It just replaces the dummy value and not the Key(Value for HashSet).
Look at the below code for HashSet Class:
public boolean [More ...] add(E e) {
return map.put(e, PRESENT)==null;
}
Here e is the value for HashSet but key for underlying map.and key is never replaced. Hope i am able to clear the confusion.
You need to check put method in Hash map first as HashSet is backed up by HashMap
When you add duplicate value say a String "One" into HashSet,
An entry ("one", PRESENT) will get inserted into Hashmap(for all the
values added into set, the value will be "PRESENT" which if of type Object)
Hashmap adds the entry into Map and returns the value, which is in this case
"PRESENT" or null if Entry is not there.
Hashset's add method then returns true if the returned value from Hashmap equals
null otherwise false which means an entry already exists...
To say it differently: When you insert a key-value-pair into a HashMap where the key already exists (in a sense hashvalue() gives the same value und equal() is true, but the two objects can still differ in several ways), the key isn't replaced but the value is overwritten. The key is just used to get the hashvalue() and find the value in the table with it.
Since HashSet uses the keys of a HashMap and sets arbitrary values which don't really matter (to the user) as a result the Elements of the Set aren't replaced either.
The simple way to think about it is that if you look at the add method of hashset you will see an optional return type of boolean T / F. The return type as false is meaningful only if the hashset is not able to add the element.

Understanding HashMap<K,V>

Ok, here is the bit I do not understand.
If you attempt to retrieve an object using the get() method and null is returned, it is still possible that null may be stored as the object associated with the key you supplied to the get() method. You can determine if this is the case by passing your key of the object to containsKey() method for map. This returns true if key is stored in the map
So, how is containsKey() supposed to tell me if the value associated with the key supplied is null?
This is the reference if you wanna check. Page 553
Map<String, Object> map = new HashMap<String, Object>();
map.put("Foo", null);
System.out.println(map.containsKey("Foo"));
System.out.println(map.containsKey("Boo"));
OUTPUT:
true
false
get() returns null in two cases:
The key does not exist in the map.
The key does exist but the associated value is null.
You can't tell from get() which is true. However, containsKey() will tell you if the key was present in the map, regardless of whether its associated value was null.
Consider this simple snippet of code:
Map<String, String> m = new HashMap<String, String>();
m.put("key1", "value1");
m.put("key2", null);
System.out.println("m.get(\"key1\")=" + m.get("key1"));
System.out.println("m.containsKey(\"key1\")=" + m.containsKey("key1"));
System.out.println("m.get(\"key2\")=" + m.get("key2"));
System.out.println("m.containsKey(\"key2\")=" + m.containsKey("key2"));
System.out.println("m.get(\"key3\")=" + m.get("key3"));
System.out.println("m.containsKey(\"key3\")=" + m.containsKey("key3"));
As you can see I put in the map two values, one of which is null. Thene i asked the map for three values: two of them are present (one is null), one is not. Look at the result:
m.get("key1")=value1
m.containsKey("key1")=true
m.get("key2")=null
m.containsKey("key2")=true
m.get("key3")=null
m.containsKey("key3")=false
The second and the third are the tricky part. key2 is present with null value so, using get() you cannot discriminate whether the element is not in the map or is in the map with a null value. But, using containsKey() you can, as it returns a boolean.
(get() == null && containsKey()) == value is null
containsKey would tell you if the key is in the hashmap at all. Consider the case where a key is present with null value and the other case in which the key which you are looking for is not at all in the hashmap.

Categories

Resources