Collections break - java

From this link
Name objects are immutable. All other things being equal, immutable
types are the way to go, especially for objects that will be used as
elements in Sets or as keys in Maps. These collections will break if
you modify their elements or keys while they're in the collection.
How do we know that the class "Name" is immutable? (class name visible in the link above mentioned)
What do they actually mean with "the collections will break if you modify their elements?"
Thanks in advance.

Because with mutable classes, you can change the properties based which they are organized/ordered in the Collections, and the holder class would not know about it.
Think that you could do:
public class Name implements Comparable<Name> {
private String firstname = null
// getters and setters
public int compareTo(Name name) {
// Compare based in firstName
}
}
And then:
Name name1 = new Name("John");
Name name2 = new Name("Mike");
SortedSet<Name> set = new TreeSet<Name>();
set.add(name1);
set.add(name2);
name1.setFirstName("Ralph");
Now, is set ordered or is it not?
In a similar way, changes that affect the hashCode of the instance break HashMap and similars, because the first that does these classes when inserting/retrieving objects is to use a specific bucket based in that value.

What they mean is, lookups based on object will be failed.
For example:
mylist.get(myObject);
will fail because the object reference you have will be different (due to modifications) from the one you are using to do get(...) call.

HashSet and HashMap rely on the contract for equals() and hashCode described in the javadoc for java.lang.Object. That means that for two objects being equal accorrding to equals() the calculated hashCode() must also be equal.
If the hashCode() for a object in a Set or Map changes during the time the object is in the Set or Map the implementation will not find the object as it is saved in the bucket for the old hashCode().
Therefore changing the hashCode() while an object is in a Set or Map is a really bad idea.

From the docs on Map
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map. A special case of this prohibition is that it is not permissible for a map to contain itself as a key. While it is permissible for a map to contain itself as a value, extreme caution is advised: the equals and hashCode methods are no longer well defined on such a map.
From the docks on Set
Note: 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. A special case of this prohibition is that it is not permissible for a set to contain itself as an element.
Lookups are done with .equals on the keys, if the keys are mutable the lookup will fail.

Related

Why is it preferred to use immutable objects as elements of a Set?

I read it here:
Immutable objects are good Map keys and Set elements, since these
typically do not change once created.
Because these collections rely on hashing, fields that contribute to their hashCode should be immutable.
When a HashMap wants to store a key-value, it uses hashCode of its key and works out a place for the pair,
The same technique will be used for elements retrieval ( ex: contains,get, etc..). Now imagine hashCode upon element retrieval produces a value different than the one produced at the time the elements were added ? Would we be able to locate the element correctly? No.
HashSet is no different from a HashMap.
It's all about having a hashCode and equals methods that are able to compare objects correctly, immutability makes it easier to reason about the correctness of these methods.

Can a StringBuffer be used as a key in a HashMap?

Can a StringBuffer as a key in a HashMap?
If so, what is the difference between using a String and a StringBuffer as the key?
Can a StringBuffer as a key in a HashMap?
No, since StringBuffer overrides neither equals nor hashCode, so it is not suitable as a HashMap key (recall that HashMap relies on those two methods to tell if a given key is present in the map).
Beyond that, StringBuffers are mutable, and you typically want Map keys to be immutable. From Map:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map. A special case of this prohibition is that it is not permissible for a map to contain itself as a key. While it is permissible for a map to contain itself as a value, extreme caution is advised: the equals and hashCode methods are no longer well defined on such a map.
No, you cannot, unless you want to distinguish between separate buffers instead of their contents. The StringBuffer class does not implement equals or hashCode which means it inherits these methods from Object. The Object implementation of these methods only distinguishes between object instances, not their contents.
In other words, if you would have two StringBuffer instances with the same contents, they would not be considered equal. Even weirder, if you would reinsert the same buffer with a different value, it would be considered equal to the previous one.
In general you should take care using mutable values as keys. Mutations will not alter the position in the Map, as the Map instance will not be notified of the change. In this case, since equals is not implemented anyway, this issue will not come up.
All classes in java are intended to be used as hash keys, because all of them inherit the supermethod hashCode. Altough there are some cases in which, though it might compile well, would be quite weird, like Connection or Streams... or StringBuffer. This is why:
The main difference between String and StringBuffer is that a String is immutable by design, and it contains a proper implementation of hashCode. StringBuffers, instead, may change, and because of this, this class does not have a proper implementation of hashCode: It does not override the default implementation inherited from Object. Now you can see the consequences: A StringBuffer cannot contain a hash of high quality, nor coherent with its contents, damaging then the result of the hashing algorithm.
Yes, any object can be used as a key in a HashMap, although that may not be a good idea.
Class HashMap
Type Parameters:
K - the type of keys maintained by this map
V - the type of mapped values
From this SO answer:
When you put a key-value pair into the map, the hashmap will look at
the hash code of the key, and store the pair in the bucket of which
the identifier is the hash code of the key. (...) Looking at the above
mechanism, you can also see what requirements are necessary on the
hashCode() and equals() methods of keys (...)
Do notice, however, that StringBuffer does not override the required methods so your "key" will be the object's memory address. From the hashcode() docs:
(This is typically implemented by converting the internal address of
the object into an integer, but this implementation technique is not
required by the JavaTM programming language.)
Meaning it's use as a key will be very different than String's:
Map<String, String> hashA = new HashMap<>();
a.put('a', 'a');
System.out.println(hashA.get('a')); //prints 'a'
Map<StringBuffer, String> hashB = new HashMap<>();
StringBuffer buffer = new StringBuffer('a');
hashB.put(buffer, 'a');
System.out.println(hashB.get(new StringBuffer('a'))); //prints null
System.out.println(hashB.get(buffer)); //prints 'a'

How does Java determine uniqueness of Hashmap key?

I want to maintain a list of objects such that each object in the list is unique.Also I want to retrieve it at one point. Objects are in thousands and I can't modify their source to add a unique id. Also hascodes are unreliable.
My approach was to utilize the key uniqueness of a map.
Say a maintain a map like :
HashMap<Object,int> uniqueObjectMap.
I will add object to map with as a key and set a random int as value. But how does java determine if the object is unique when used as a key ?
Say,
List listOne;
List listTwo;
Object o = new Object;
listOne.add(o);
listTwo.add(o);
uniqueObjectMap.put(listOne.get(0),randomInt()); // -- > line 1
uniqueObjectMap.put(listTw0.get(0),randomInt()); // --> line 2
Will line 2 give an unique key violation error since both are referring to the same object o ?
Edit
So if will unqiueObjectMap.containsKey(listTwo.get(0)) return true ? How are objects determined to be equal here ? Is a field by field comparison done ? Can I rely on this to make sure only one copy of ANY type of object is maintained in the map as key ?
Will line 2 give an unique key violation error since both are referring to the same object o ?
- No. If a key is found to be already present, then its value will be overwritten with the new one.
Next, HashMap has a separate hash() method which Applies a supplemental hash function to a given hashCode (of key objects), which defends against poor quality hash functions.
It does so by calling the Object's hashcode() function.
The default implementation is roughly equivalent to the object's unique identifier (much like a memory address); however, there are objects that are compare-by-value. If dealing with a compare-by-value object, hashcode() will be overridden to compute a number based on the values, such that two identical values yield the same hashcode() number.
As for the collection items that are hash based, the put(...) operation is fine with putting something over the original location. In short, if two objects yeild the same hashcode() and a positive equals(...) result, then operations will assume that they are for all practical purposes the same object. Thus, put may replace the old with the new, or do nothing, as the object is considered the same.
It may not store two copies in the same "place" as it makes no sense to store two copies at the same location. Thus, sets will only contain one copy, as will map keys; however, lists will possibly contain two copies, depending on how you added the second copy.
How are objects determined to be equal here ?
By using equals and Hashcode function of Object class.
Is a field by field comparison done ?
No, if you dont implement equals and hashcode, java will compare the references of your objects.
Can I rely on this to make sure only one copy of ANY type of object is maintained in the map as key ?
No.
Using a Set is a better approch than using Map because it removes duplicates by his own, but in this case it wont work either because Set determinates duplicates the same way like a Map does it with Keys.
If you will refer to same then it ll not throw an error because when HashMap get same key then it's related value will be overwrite.
If the same key is exist in HashMap then it will be overwritten.
if you want to check if the key or value is already exist or not then you can use:
containsKey() and containsValue().
ex :
hashMap.containsKey(0);
this will return true if the key named 0 is already exist otherwise false.
By getting hashcode value using hash(key.hashCode())
HashMap has an inner class Entry with attributes
final K key;
V value;
Entry<K ,V> next;
final int hash;
Hash value is used to calculate the index in the array for storing Entry object, there might be the scenario where 2 unequal object can have same equal hash value.
Entry objects are stored in linked list form, in case of collision, all entry object with same hash value are stored in same Linkedlist but equal method will test for true equality. In this way, HashMap ensure the uniqueness of keys.

HashMap not returning values based on key

I'm trying to use a HashMap with my class Cell as the key. However, after putting an item into the HashMap, calling contains on the item will return false.
public static void main(String args[]) {
HashMap<Cell, String> map = new HashMap<Cell, String>();
map.put(new Cell(0,0), "Bob");
System.out.println(map.containsKey(new Cell(0,0)));
System.out.println(new Cell(0,0).equals(new Cell(0,0)));
}
This prints out false and true, where it should print true and true, since according to the Map docs containsKey uses .equals(). What am I doing wrong?
This is most likely because you don't have equals() and hashCode() implemented. In Java, the rule of thumb is that if you implement one, you must implement the other. In your case, it's mandatory because HashMap makes use of them.
You created two separate objects with two separate addresses. Without these methods, the JVM has no way of knowing that the objects are "the same."
See http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode()
Consider how a HashMap is implemented. When putting, it first calculates the object hashCode() to figure out which bucket to place the object in. When it tries to retrieve an object, it again gets its hashCode(), identifies to target bucket, goes through the linked list in the bucket, calling equals() against each object. It returns if it finds a match.
In other words, when you use HashMap you need to have a correct and matching implementation of equals() and hashCode().
The default hashCode() method inherited from Object does not correctly return the same hashCode() unless the object references are the same. In your case they are not.
calling new Cell(0,0) several times produce different objects with different hash Codes. You should implement hashCode for Cell class.
You likely forgot to implement the hashcode() function for Cell, which is also required in order to use a user defined class in a HashMap. Here is a simple and generally accurate way to implement a hashcode() function:
int hashcode(){
return (field1.toString()+field2.toString()+...+fieldN.toString()).hashcode();
}
Where field1 to fieldN are the fields in your class. If the fields are primatives (ie int) just take out the toString().

Are there any constraints on the hash map data type

Are there any constraint on the key type in the hash map and hash table?----Interview Question.
I think yes we can customize it as needed.
Technically, no. Generally, you want to use an object that implements equals() and hashCode() although that is not strictly necessary. If you don't, then it will use the base implementations defined by Object which compare object identity. A lot of times, that is not appropriate but sometimes it's fine.
Technically the key doesn't need to be immutable as long as the values used in the equals() and hashCode() implementations are immutable. For example, if your class Foo uses a string "foo" as part of its has then that value "foo" must not change. That's because hash maps put the keys into buckets based on the hashCode() value for efficiency reasons. If the hashCode suddenly changes, the hash map is unaware and the key will now live in the wrong bucket and you'll run into nasty bugs because it's then possible to have "duplicate" objects in your map. Hope that makes sense.
Several things to consider:
Just about the "Type", you cannot use primitive type. This is language constraint of Java. e.g. HashMap<int, Foo> is not valid, you need to use HashMap<Integer, Foo>
Base on the way HashMap work, key should have a meaningful implementation of hashCode() and equals(). How it is "meaningful" depends on your need. It may be possible that the default implementation in Object already serve your need, but you need to aware of it.
Once an object instance is put into the Map as key, its hashCode() and equals() should stay consistent. You should never put to a map, and change the state of the object instance as Key and cause hashCode()/equals() returns different value. The easiest way to ensure it is of course use an immutable object as key. However it is still fine that you use mutable object, but in your code, you ensure changing state of keys are not happening.

Categories

Resources