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
Related
Investigating a special case where some objects didn't equal as they should and came to this simple test case that simplifies my issue.
When running this with JUnit in Eclipse with jdk8u152 the last assertEquals fails, can anyone explain why?
It's something with Set/HashSet because if I change as,bs to be ArrayList's instead the final assertEquals goes through.
#Test
public void test()
{
String list = "list";
String object = "object";
String value = "value";
Map<String, Object> a = new HashMap<>();
Map<String, Object> b = new HashMap<>();
assertEquals(a, b);
Set<Object> as = new HashSet<>();
Set<Object> bs = new HashSet<>();
a.put(list, as);
b.put(list, bs);
assertEquals(a, b);
Map<String, Object> ao = new HashMap<>();
as.add(ao);
Map<String, Object> bo = new HashMap<>();
bs.add(bo);
assertEquals(a, b);
ao.put(object, value);
bo.put(object, value);
assertEquals(a, b);
}
You're mutating the elements of the sets. That leads to unspecified behaviour.
From the JavaDoc:
Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.
You are adding ao and bo HashMaps to the HashSets as and bs.
Later you mutate ao and bo by putting a new entry in each of them.
This means that the hashCode that was used to place ao in as is no longer the current hashCode of ao, and the hashCode that was used to place bo in bs is no longer the current hashCode of bo.
As a result, AbstractSet's equals cannot locate the element of one Set in the other Set, so it concludes that as is not equal to bs. As a result a is not equal to b.
Here's the implementation of AbstractSet's equals. You can see that it uses containsAll, which in turns calls contains(), which relies on the hashCode of the searched element. Since that hashCode has changed after the element was added to the Set, contains() doesn't find the element.
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection<?> c = (Collection<?>) o;
if (c.size() != size())
return false;
try {
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
If you mutate an element of a HashSet in a way that affects the result of equals or hashCode, you must remove the element from the HashSet prior to the update and add it again after the update.
Adding the following remove and add calls will cause a to be equal to b in the end:
....
assertEquals(a, b);
bs.remove (bo); // added
as.remove (ao); // added
ao.put(object, value);
bo.put(object, value);
as.add (ao); // added
bs.add (bo); // added
assertEquals(a, b);
That is because of the hascode implementation of HashMap which is basically x-or of key and value. If key or value is null then hascode will be zero. Hence all empty hashmaps will have hashcode as zero.
/*hashcode of HashMap*/
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
/*hashcode of object*/
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
Upon adding key value pairs the hashcode value changes.
I'd like to get the "canonical" key object for each key usable to query a map. See here:
Map<UUID, String> map = new HashMap();
UUID a = new UUID("ABC...");
map.put(a, "Tu nejde o zamykání.");
UUID b = new UUID("ABC...");
String string = map.get(b); // This gives that string.
// This is what I am looking for:
UUID againA = map.getEntry(b).key();
boolean thisIsTrue = a == againA;
A HashMap uses equals(), which is the same for multiple unique objects. So I want to get the actual key from the map, which will always be the same, no matter what object was used to query the map.
Is there a way to get the actual key object from the map? I don't see anything in the interface, but perhaps some clever trick I overlooked?
(Iterating all entries or keys doesn't count.)
Is there a way to get the actual key object from the map?
OK, so I am going to make some assumptions about what you mean. After all, you said that your question doesn't need clarification, so the obvious meaning that I can see must be the correct one. Right? :-)
The answer is No. There isn't a way.
Example scenario (not compileable!)
UUID uuid = UUID.fromString("xxxx-yyy-zzz");
UUID uuid2 = UUID.fromString("xxxx-yyy-zzz"); // same string
println(uuid == uuid2); // prints false
println(uuid.equals(true)); // prints true
Map<UUID, String> map = new ...
map.put(uuid, "fred");
println(map.get(uuid)); // prints fred
println(map.get(uuid2)); // prints fred (because uuid.equals(uuid2) is true)
... but, the Map API does not provide a way to find the actual key (in the example above it is uuid) in the map apart from iterating the key or entry sets. And I'm not aware of any existing Map class (standard or 3rd-party) that does provide this1.
However, you could implement your own Map class with an additional method for returning the actual key object. There is no technical reason why you couldn't, though you would have more code to write, test, maintain, etcetera.
But I would add that I agree with Jim Garrison. If you have a scenario where you have UUID objects (with equality-by-value semantics) and you also want to implement equality by identity semantics, then there is probably something wrong with your application's design. The correct approach would be to change the UUID.fromString(...) implementation to always return the same UUID object for the same input string.
1 - This is not to say that such a map implementation doesn't exist. But if it does, you should be able to find it if you look hard enough Note that Questions asking us to find or recommend a library are off-topic!
There is a (relatively) simple way of doing this. I’ve done so in my applications from time to time, when needed ... not for the purpose of == testing, but to reduce the number of identical objects being stored when tens of thousand of objects exist, and are cross-referenced with each other. This significantly reduced my memory usage, and improved performance ... while still using equals() for equality tests.
Just maintain a parallel map for interning the keys.
Map<UUID, UUID> interned_keys = ...
UUID key = ...
if (interned_keys.contains(key))
key = interned_keys.get(key)
Of course, it is far better when the object being stored knows what its own identity is. Then you get the interning basically for free.
class Item {
UUID key;
// ...
}
Map<UUID, Item> map = ...
map.put(item.key, item);
UUID key = ...
key = map.get(key).key; // get interned key
I think there are valid reasons for wanting the actual key. For example, to save memory. Also keep in mind that the actual key may store other objects. For instance, suppose you have a vertex of a graph. The vertex can store the actual data (Say a String, for instance), as well as the incident vertices. A vertex hash value can be dependent only on the data. So to look up a vertex with some data,
D, look up a vertex with data, D,and with with no incident values. Now if you can return the actual vertex in the map you will be able to get the actual incident to the vertex.
It seems to me that many map implementations could easily provide a getEntry method. For example, the HashMap implementation for get is:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
One could use the getNode method to return an Entry:
public getEntry(Object key){
Node<K,V> e = getNode(hash(key),key);
if(e == null) return null;
return new Entry<>(e.key,e.value);
}
The easiest way is to duplicate the reference to the key in the value using a generic Pair type, like this:
HashMap<UUID,Pair<UUID,String>> myMap = new HashMap<>();
When you put them in the map, you provide the reference to the key to the pair. The cost is one reference per entry.
void add(UUID uuid, String str)
{
myMap.put(uuid,Pair.of(uuid,str));
}
Pair<UUID,String> get(UUID uuid)
{
return myMap.get(uuid);
}
Then getFirst() of the Pair is your key. getSecond() is the value.
Whatever you do, it's going to cost you in either time or space.
Your Pair class will be something like:
public class Pair<A,B>
{
private final A a;
private final B b;
public Pair(A a, B b)
{
this.a = a;
this.b = b;
}
/**
* #return the first argument of the Pair
*/
public A getFirst()
{
return this.a;
}
/**
* #return the second argument of the Pair
*/
public B getSecond()
{
return this.b;
}
/**
* Create a Pair.
*
* #param a The first argument (of type A)
* #param b The second argument (of type B)
*
* #return A Pair of A and B
*/
public static <A,B> Pair<A,B> of(A a, B b)
{
return new Pair<>(a,b);
}
// Don't forget to get your IDE to produce a hashcode()
// and equals() method for you, depending
// on if you allow nulls or not, or DIY.
}
it could help. You can use a for each like below.
Map<String,Object> map = new HashMap<>();
map.put("hello1", new String("Hello"));
map.put("hello2", new String("World"));
map.put("hello3", new String("How"));
map.put("hello4", new String("Are u"));
for(Map.Entry<String,Object> e: map.entrySet()){
System.out.println(e.getKey());
}
When using a hash map, it's important to evenly distribute the keys over the buckets.
If all keys end up in the same bucket, you essentially end up with a list.
Is there a way to "audit" a HashMap in Java in order to see how well the keys are distributed?
I tried subtyping it and iterating Entry<K,V>[] table, but it's not visible.
I tried subtyping it and iterating Entry[] table, but it's not visible
Use Reflection API!
public class Main {
//This is to simulate instances which are not equal but go to the same bucket.
static class A {
#Override
public boolean equals(Object obj) { return false;}
#Override
public int hashCode() {return 42; }
}
public static void main(String[] args) {
//Test data
HashMap<A, String> map = new HashMap<A, String>(4);
map.put(new A(), "abc");
map.put(new A(), "def");
//Access to the internal table
Class clazz = map.getClass();
Field table = clazz.getDeclaredField("table");
table.setAccessible(true);
Map.Entry<Integer, String>[] realTable = (Map.Entry<Integer, String>[]) table.get(map);
//Iterate and do pretty printing
for (int i = 0; i < realTable.length; i++) {
System.out.println(String.format("Bucket : %d, Entry: %s", i, bucketToString(realTable[i])));
}
}
private static String bucketToString(Map.Entry<Integer, String> entry) throws Exception {
if (entry == null) return null;
StringBuilder sb = new StringBuilder();
//Access to the "next" filed of HashMap$Node
Class clazz = entry.getClass();
Field next = clazz.getDeclaredField("next");
next.setAccessible(true);
//going through the bucket
while (entry != null) {
sb.append(entry);
entry = (Map.Entry<Integer, String>) next.get(entry);
if (null != entry) sb.append(" -> ");
}
return sb.toString();
}
}
In the end you'll see something like this in STDOUT:
Bucket : 0, Entry: null
Bucket : 1, Entry: null
Bucket : 2, Entry: Main$A#2a=abc -> Main$A#2a=def
Bucket : 3, Entry: null
HashMap uses the keys produced by the hashCode() method of your key objects, so I guess you are really asking how evenly distributed those hash code values are. You can get hold of the key objects using Map.keySet().
Now, the OpenJDK and Oracle implementations of HashMap do not use the key hash codes directly, but apply another hashing function to the provided hashes before distributing them over the buckets. But you should not rely on or use this implementation detail. So you ought to ignore it. So you should just ensure that the hashCode() methods of your key values are well distributed.
Examining the actual hash codes of some sample key value objects is unlikely to tell you anything useful unless your hash cide method is very poor. You would be better doing a basic theoretical analysis of your hash code method. This is not as scary as it might sound. You may (indeed, have no choice but to do so) assume that the hash code methods of the supplied Java classes are well distributed. Then you just need a check that the means you use for combining the hash codes for your data members behaves well for the expected values of your data members. Only if your data members have values that are highly correlated in a peculiar way is this likely to be a problem.
You can use reflection to access the hidden fields:
HashMap map = ...;
// get the HashMap#table field
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(map);
int[] counts = new int[table.length];
// get the HashMap.Node#next field
Class<?> entryClass = table.getClass().getComponentType();
Field nextField = entryClass.getDeclaredField("next");
nextField.setAccessible(true);
for (int i = 0; i < table.length; i++) {
Object e = table[i];
int count = 0;
if (e != null) {
do {
count++;
} while ((e = nextField.get(e)) != null);
}
counts[i] = count;
}
Now you have an array of the entry counts for each bucket.
Client.java
public class Client{
public static void main(String[] args) {
Map<Example, Number> m = new HashMap<>();
Example e1 = new Example(100); //point 1
Example e2 = new Example(200); //point2
Example e3 = new Example(300); //point3
m.put(e1, 10);
m.put(e2, 20);
m.put(e3, 30);
System.out.println(m);//point4
}
}
Example.java
public class Example {
int s;
Example(int s) {
this.s =s;
}
#Override
public int hashCode() {
// TODO Auto-generated method stub
return 5;
}
}
Now at point 1, point 2 and point 3 in Client.java, we are inserting 3 keys of type Example in hashmap m. Since hashcode() is overridden in Example.java, all three keys e1,e2,e3 will return same hashcode and hence same bucket in hashmap.
Now the problem is how to see the distribution of keys.
Approach :
Insert a debug point at point4 in Client.java.
Debug the java application.
Inspect m.
Inside m, you will find table array of type HashMap$Node and size 16.
This is literally the hashtable. Each index contains a linked list of Entry objects that are inserted into hashmap. Each non null index has a hash variable that correspond to the hash value returned by the hash() method of Hashmap. This hash value is then sent to indexFor() method of HashMap to find out the index of table array , where the Entry object will be inserted. (Refer #Rahul's link in comments to question to understand the concept of hash and indexFor).
For the case, taken above, if we inspect table, you will find all but one key null.
We had inserted three keys but we can see only one, i.e. all three keys have been inserted into the same bucket i.e same index of table.
Inspect the table array element(in this case it will be 5), key correspond to e1, while value correspond to 10 (point1)
next variable here points to next node of Linked list i.e. next Entry object which is (e2, 200) in our case.
So in this way you can inspect the hashmap.
Also i would recommend you to go through internal implementation of hashmap to understand HashMap by heart.
Hope it helped..
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.
I have a class that has a Map<String, Object> field (the keys are Strings, the values are Objects that have correctly implemented the "equals" method for comparison).
I would like to override equals for this class in a way that only returns true if the Maps have equal mappings between keys and values.
Here is my attempt:
// Assumes that the Object values in maps have correctly implemented the equals method.
private boolean mapsEqual(Map<String, Object> attributes)
{
if (this.attributes_.keySet().size() != attributes.keySet().size() ||
this.attributes_.values().size() != attributes.values().size())
return false;
for (String key : attributes.keySet()) {
if (!this.attributes_.keySet().contains(key))
return false;
if (!this.attributes_.get(key).equals(attributes.get(key)))
return false;
}
return true;
}
However, this implementation fails when the same key is added more than once or when a key is removed from the map (the size tests fail for the values, as they count the duplicates and do not resize when values are removed.)
It seems that my situation should be common enough to find information that is relevant to my case, but I could not find any. Is there any legacy code or widely accepted solution to this situation? Any help or working solution is appreciated.
I am going to put this as an answer even though I am not 100% sure it solves your problem (but it's simply not gonna fit in a comment).
First off, to repeat my comments: The Map interface forbides that a map has duplicate keys or multiple values per key. Any proper implementation (e.g. java.util.HashMap) will therefore not allow this. Typically they will just replace the value if this happens.
Furthermore, the specification for equals, to me, seems to be doing what you want. Again, a proper implementation must live up to that specification.
So, what's the point here: If you are writing your own class that is implementing Map, then it simply cannot allow duplicate keys (methods like get wouldn't make sense anymore). If you are using a built-in implementation such as HashMap, it replaces the values anyway.
Now you are saying that you're experiencing size issues with keySet() and values(). I think you should add example code that will cause this behavior. The following works just fine for me:
Map<String, String> map = new HashMap<String, String>();
map.put("Foo", "Bar");
System.out.println(map.keySet().size()); // 1
System.out.println(map.values().size()); // 1
map.put("Foo", "Baz"); // the HashMap will merely replace the old value
System.out.println(map.keySet().size()); // still 1
System.out.println(map.values().size()); // still 1
Removing a key will, of course, change the size. I don't see how you consider this a problem based on your explanations so far.
As for equals, you may just want to look at the implementation for HashMap, which can be found here:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<K,V> m = (Map<K,V>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
Consider the following example:
Map<String, String> map1 = new HashMap<String, String>();
map1.put("Foo", "Bar");
Map<String, String> map2 = new HashMap<String, String>();
map2.put("Foo", "Bar");
System.out.println(map1.equals(map2)); // true
Firstly, you complain about your maps having duplicate keys... not possible (unless you're using a badly broken implementation).
This should do it:
public boolean equals(Object o) {
if (!(o instanceof MyClass))
return false;
MyClass that = (MyClass)o;
if (map.size() != that.map.size())
return false;
for (Map.Entry<String, Object> entry : map) {
Object a = entry.getValue();
Object b = that.map.get(entry.getKey());
if ((a == null ^ b == null) || (a == null && !a.equals(b)))
return false;
}
return true;
}