Why Map.putIfAbsent() is returning null? - java

The following program is printing null. I am not able to understand why.
public class ConcurrentHashMapTest {
public static final Map<String, String> map = new ConcurrentHashMap<>(5, 0.9f, 2);
public static void main(String[] args) {
map.putIfAbsent("key 1", "value 1");
map.putIfAbsent("key 2", "value 2");
String value = get("key 3");
System.out.println("value for key 3 --> " + value);
}
private static String get(final String key) {
return map.putIfAbsent(key, "value 3");
}
}
Could someone help me understand the behavior?

Problem is that by definition putIfAbsent return old value and not new value (old value for absent is always null). Use computeIfAbsent - this will return new value for you.
private static String get(final String key) {
return map.computeIfAbsent(key, s -> "value 3");
}

ConcurrentMap.putIfAbsent returns the previous value associated with the specified key, or null if there was no mapping for the key. You did not have a value associated with "key 3". All correct.
NOTE: Not just for ConcurrentMap, this applies to all implementations of Map.

putIfAbsent() returns the previous value associated with the specified key, or null if there was no mapping for the key, and because key 3 is not present in the map so it returns null.
You have added key 1 and key 2 in the map but key 3 is not associated with any value. So you get a null. Map key 3 with some value and putIfAbsent() will return previous value associated with that key.
Like if map already contained key 3 associated with value A
key 3 ---> A
Then on calling map.putIfAbsent("key 3","B") will return A

This is a frequently asked question, which perhaps suggest this behaviour is unintuitive. Maybe the confusion comes from the way dict.setdefault() works in python and other languages. Returning the same object you just put helps cut a few lines of code.
Consider:
if (map.contains(value)){
obj = map.get(key);
}
else{
obj = new Object();
}
versus:
obj = map.putIfAbsent(key, new Object());

It's in the javadoc:
returns the previous value associated with the specified key, or null if there was no mapping for the key

Please read the documentation of ConcurrentHashMap.putIfAbsent:
Returns:
the previous value associated with the specified key, or null if there was no mapping for the key
As there was no previous value for the key "key 3", it returns null.

If you look at the documentation, it says
Returns: the previous value associated with the specified key, or
null if there was no mapping for the key
In your case, no value was previously associated with the key, hence NULL

The current mapped value could be returned by using merge function. The following could would return the current non-null value if the key already exists, or returns the given new value if a mapping does not exist or if the value is null.
private static String get(final String key) {
return map.merge(key, "value 3", (oldVal, newVal) -> oldVal);
}
or in general:
private T get(final String key, T value) {
return map.merge(key, value, (oldVal, newVal) -> oldVal);
}
This could be useful when you do not prefer to use computeIfAbsent because the mapping function to computeIfAbsent could throw an exception, and you also do not want to do the below:
map.putIfAbsent(key, value);
return map.get(key);

All the answers are correct, and just to add a side note,
If the specified key is not already associated with a value (or is
mapped to null) associates it with the given value and returns null,
else returns the current value.
public V putIfAbsent(K key, V value) {
return putVal(key, value, true); }
The key maintains in the table. The value can be retrieved by calling the get method with a key that is equal to the original key before put. If the key is not found in the table then returns null.

Related

LinkedHashMap: replace method fails to replace value for spacified key

I have a linked hash map which stores random 6 char string as a key and 30 char string as values. When I call replace method, it is supposed to replace value for given key and return existing value associated with given key.
Code
Map cache = new LinkedHashMap<String, String>();
protected boolean registerCache(String key, String val) {
System.out.println("Registering key "+ key +" associated with : "+val);
String result = cache.put(key, val);
System.out.println("Replacement result "+result);
return result == null;
}
protected synchronized boolean updateCache(String key, String val) {
System.out.println("map before replace : "+cache.toString());
String replaced = cache.replace(key, val);
System.out.println("replacing "+replaced+" with "+val);
return replaced != null;
}
Register cache stores key value for first time and then update method is supposed to replace value for registered key.
But once in 4 times, it fails to replace. It behaves as key was never registered. Here is output:
Registering key \b?}`& associated with : Vtw7vd3Mtk9DEImmZAxfazKrckVpt4
Replacement result null
map before replace: {
d\ZDO<=9pw7cEjdnvWhpbxar564kiSkVpt4Z1,
pHQ)j\=9pw7cEjdnvWhpbxar564kiSkVpt4Z1,
0''nEY=KxE7vdInrD2goNOU5LdMFdEMgsCh-1,
C\Gude=KxE7vdInrD2goNOU5LdMFdEMgsCh-1,
\b?}`&=Vtw7vd3Mtk9DEImmZAxfazKrckVpt4}
replacing null with KxE7vdInrD2goNOU5LdMFdEMgsCh-1
Please suggest if I am doing something wrong. I suspect the key generated should not be random char string.
I figured out reason.
As suggested by Thomas in comment above, I added more sysout for Keys against which value needs to be replaced.
I was using RandomStringUtils.randomAscii(6) (from commons library) for generating key. The key generated had some chars which were printed with spaces and hence the key was not properly found to replace value against it.
When I replaced RandomStringUtils.randomAscii(6) with RandomStringUtils.randomAlphanumeric(8), it's behaving as expected.

How to SUM duplicate key values in LinkedHashMap?

I have a LinkedHashMap where I have two duplicate keys with their correspondent values, I need to know how to SUM those values into one key. Currently he eliminates the old duplicated value and put the new one
This is my Map
static Map<String, Double> costByDuration = new LinkedHashMap<>();
This is where I put the values ( call_from can be 912345678 and have a value of 10, and then another call from 912345678 and have a value of 20), then I want 912345678 to have a value of 30 instead of keeping only one.
costByDuration.put(call_from, toPay);
I'd create a method as follows:
public void put(String key, Double value){
costByDuration.merge(key,value , Double::sum);
}
then the use case would be:
put(call_from, toPay);
put(anotherKey, anotherValue);
...
...
This solution internally uses the merge method which basically says if the specified key is not already associated with a value or is associated with null, associates it with the given non-null value. Otherwise, replaces the associated value with the results of the given remapping function.
You'll have to check first whether your value is already in the map.
Double existingValue = costByDuration.get(callFrom);
if (existingValue != null) {
costByDuration.put(callFrom, existingValue + toPay);
} else {
costByDuration.put(callFrom, toPay);
}
Incidentally, it's a bad idea to use a Double to store an amount of money, if you want your arithmetic operations to give you the correct answer. I strongly recommend using BigDecimal in place of Double.
Use merge function:
costByDuration.merge(call_from, toPay, (oldPay, toPay) -> oldPay + toPay);
Try this using containsKey method :
static Map<String, Double> costByDuration = new LinkedHashMap<>();
if(costByDuration.containsKey(call_from) {
costByDuration.put(call_from, map.get(call_from) + to_Pay);
} else {
costByDuration.put(call_from, to_Pay);
}

Java's HashMap key replacement when storing existing value

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

Get key from a HashMap using the value [duplicate]

This question already has answers here:
Java Hashmap: How to get key from value?
(39 answers)
Closed 5 years ago.
I want to get the key of a HashMap using the value.
hashmap = new HashMap<String, Object>();
haspmap.put("one", 100);
haspmap.put("two", 200);
Which means i want a function that will take the value 100 and will return the string one.
It seems that there are a lot of questions here asking the same thing but they don't work for me.
Maybe because i am new with java.
How to do it?
The put method in HashMap is defined like this:
Object put(Object key, Object value)
key is the first parameter, so in your put, "one" is the key. You can't easily look up by value in a HashMap, if you really want to do that, it would be a linear search done by calling entrySet(), like this:
for (Map.Entry<Object, Object> e : hashmap.entrySet()) {
Object key = e.getKey();
Object value = e.getValue();
}
However, that's O(n) and kind of defeats the purpose of using a HashMap unless you only need to do it rarely. If you really want to be able to look up by key or value frequently, core Java doesn't have anything for you, but something like BiMap from the Google Collections is what you want.
We can get KEY from VALUE. Below is a sample code_
public class Main {
public static void main(String[] args) {
Map map = new HashMap();
map.put("key_1","one");
map.put("key_2","two");
map.put("key_3","three");
map.put("key_4","four");
System.out.println(getKeyFromValue(map,"four"));
}
public static Object getKeyFromValue(Map hm, Object value) {
for (Object o : hm.keySet()) {
if (hm.get(o).equals(value)) {
return o;
}
}
return null;
}
}
I hope this will help everyone.
If you need only that, simply use put(100, "one"). Note that the key is the first argument, and the value is the 2nd.
If you need to be able to get by both the key and the value, use BiMap (from guava)
You have it reversed. The 100 should be the first parameter (it's the key) and the "one" should be the second parameter (it's the value).
Read the javadoc for HashMap and that might help you: HashMap
To get the value, use hashmap.get(100).
You mixed the keys and the values.
Hashmap <Integer,String> hashmap = new HashMap<Integer, String>();
hashmap.put(100, "one");
hashmap.put(200, "two");
Afterwards a
hashmap.get(100);
will give you "one"
if you what to obtain "ONE" by giving in 100 then
initialize hash map by
hashmap = new HashMap<Object,String>();
haspmap.put(100,"one");
and retrieve value by
hashMap.get(100)
hope that helps.
public class Class1 {
private String extref="MY";
public String getExtref() {
return extref;
}
public String setExtref(String extref) {
return this.extref = extref;
}
public static void main(String[] args) {
Class1 obj=new Class1();
String value=obj.setExtref("AFF");
int returnedValue=getMethod(value);
System.out.println(returnedValue);
}
/**
* #param value
* #return
*/
private static int getMethod(String value) {
HashMap<Integer, String> hashmap1 = new HashMap<Integer, String>();
hashmap1.put(1,"MY");
hashmap1.put(2,"AFF");
if (hashmap1.containsValue(value))
{
for (Map.Entry<Integer,String> e : hashmap1.entrySet()) {
Integer key = e.getKey();
Object value2 = e.getValue();
if ((value2.toString()).equalsIgnoreCase(value))
{
return key;
}
}
}
return 0;
}
}
If you are not bound to use Hashmap, I would advise to use pair< T,T >.
The individual elements can be accessed by first and second calls.
Have a look at this http://www.cplusplus.com/reference/utility/pair/
I used it here : http://codeforces.com/contest/507/submission/9531943

Updating a java map entry

I'm facing a problem that seems to have no straighforward solution.
I'm using java.util.Map, and I want to update the value in a Key-Value pair.
Right now, I'm doing it lik this:
private Map<String,int> table = new HashMap<String,int>();
public void update(String key, int val) {
if( !table.containsKey(key) ) return;
Entry<String,int> entry;
for( entry : table.entrySet() ) {
if( entry.getKey().equals(key) ) {
entry.setValue(val);
break;
}
}
}
So is there any method so that I can get the required Entry object without having to iterate through the entire Map? Or is there some way to update the entry's value in place? Some method in Map like setValue(String key, int val)?
jrh
Use
table.put(key, val);
to add a new key/value pair or overwrite an existing key's value.
From the Javadocs:
V put(K key, V value): Associates the specified value with the specified key in this map (optional operation). If the map previously contained a mapping for the key, the old value is replaced by the specified value. (A map m is said to contain a mapping for a key k if and only if m.containsKey(k) would return true.)
If key is present table.put(key, val) will just overwrite the value else it'll create a new entry. Poof! and you are done. :)
you can get the value from a map by using key is table.get(key); That's about it
You just use the method
public Object put(Object key, Object value)
if the key was already present in the Map then the previous value is returned.

Categories

Resources