I need a clarification regarding the use of TreeMap and LinkedList. Do these two structures use compareTo or equals?
In particular, TreeMap keeps the order in the keys, I suppose using the compareTo method of the class defined for the keys. However, when using get, do they use compareTo or equals to see if the key you pass is contained?
I have the same doubt for contains and getIndex inside LinkedList.
TreeMap uses compareTo, and the documentation warns you of problems if compareTo is not consistent with equals (i.e. that a.compareTo(b) == 0 <=> a.equals(b) should be true).
Note that the ordering maintained by a tree map ... must be consistent with equals if this sorted map is to correctly implement the Map interface.
LinkedList uses equals.
The reason why TreeMap has to use an ordering consistent with equals is that the contract of Map defines the behavior in terms of equals. For example, containsKey is defined as:
returns true if and only if this map contains a mapping for a key k such that (key==null ? k==null : key.equals(k))
Let's say you define a class like this:
class Bad implements Comparable<Bad> {
#Override public int compareTo(Bad other) { return 0; }
}
If you were to write:
Bad b1 = new Bad();
Bad b2 = new Bad();
Then:
Map<Bad, String> hm = new HashMap<>();
hm.put(b1, "");
System.out.println(hm.containsKey(b2)); // false
whereas
Map<Bad, String> tm = new TreeMap<>();
tm.put(b1, "");
System.out.println(tm.containsKey(b2)); // true
despite the fact that
System.out.println(tm.keySet().stream().anyMatch(k -> k.equals(b2))); // false
Thus, TreeMap violates the contract of Map, because Bad does not implement Comparable consistently with equals.
The Javadoc of TreeMap and LinkedList answers this:
V java.util.TreeMap.get(Object key)
Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
More formally, if this map contains a mapping from a key k to a value v such that key compares equal to k according to the map's ordering, then this method returns v; otherwise it returns null. (There can be at most one such mapping.)
and
boolean java.util.LinkedList.contains(Object o)
Returns true if this list contains the specified element. More formally, returns true if and only if this list contains at least one element e such that (o==null ? e==null : o.equals(e)).
So, for TreeMap the Comparator\Comparable implementation is used to determine equality of keys, while for LinkedLists equals is used.
Related
var arr1 = new ArrayList<Integer>();
var arr2 = new ArrayList<Integer>();
System.out.println(arr1.hashCode());
System.out.println(arr2.hashCode());
System.out.println(arr1.equals(arr2));
Output:
1
1
true
After searching, I found this answer which explains why this is happening.
From the answer:
Here's the Java 8 implementation (it's actually implemented in
AbstractList) :
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
And since the two arrays are empty, it's obvious that the hashCode function would return 1 for both of the arrays.
My question is, given two ArrayLists, how to determine whether they are the same or not? How can I be sure that modifing one ArrayList won't affect the other? since I have no unique hash for each ArrayList, I can't compare their hashes to know whether they're equal or not. How to get a unique hash for each object?
Also why the equals function returns true? Does it compare objects by hashes? Are they actually the same object? If yes then why and how to make different objects then?
And what are the classes other than ArrayList that acts in this way?
I use a class derived from TreeMap with my own comparator as keys in a LinkedHashMap. Working with this construct I found some weird behaviour I could not explain myself. Maybe one of you can help. I tried to reproduce my issue with primitives. When I create a TreeMap of primitive types, the natural sort ordering should suffice and do not need a comparator in the constructor of the TreeMap, right?!
Here is the MWE:
package treemapputtest;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public class TreeMapPutTest {
public static void main(String[] args) {
System.out.println("simple:");
simpleTest();
System.out.println("\n\ncomplex:");
complexTest();
}
private static void simpleTest(){
TreeMap<Integer,String> map = new TreeMap<>();
System.out.println("map: " + map.hashCode() + " | " + Integer.toHexString(map.hashCode()));
map.put(1, "a");
System.out.println("map: " + map.hashCode() + " | " + Integer.toHexString(map.hashCode()));
map.put(2, "b");
System.out.println("map: " + map.hashCode() + " | " + Integer.toHexString(map.hashCode()));
}
private static void complexTest(){
TreeMap<Integer,String> internalMap = new TreeMap<>();
internalMap.put(1, "a");
internalMap.put(2, "b");
System.out.println("prior: " + internalMap.hashCode() + " | " + Integer.toHexString(internalMap.hashCode()));
LinkedHashMap<TreeMap<Integer,String>,Double> myMap = new LinkedHashMap<>();
myMap.put(internalMap, 1.0);
doSomethingWithMyInternalMap(myMap.keySet().iterator().next());
System.out.println("after:");
for (Map.Entry<TreeMap<Integer,String>,Double> entry : myMap.entrySet()){
System.out.println(" " + Integer.toHexString(entry.getKey().hashCode()));
}
}
private static void doSomethingWithMyInternalMap(TreeMap<Integer,String> intern){
intern.put(3, "c");
}
}
The output is:
simple:
map: 0 | 0
map: 96 | 60
map: 192 | c0
complex:
prior: 192 | c0
after:
120
So my question is: Why does the result of hashCode() change when I add stuff to the TreeMap? For the TreeMap alone this is not a big deal, but as this creates a "new object"/the reference to the old object is changed, I get errors after updating the TreeMap in the LinkedHashMap.
The Object API says for hashCode():
The general contract of hashCode is: Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.
Does putting additional stuff in the TreeMap change something in the equals() method of TreeMap? Do I have to somehow override equals() and hashCode()?
I think you have misunderstanding about hashCode here. Let's emphasize the point in the text you quoted here:
The general contract of hashCode is: Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.
Whenever you add (or remove) data in a map, you're changing the information used in its equals method - an empty map isn't equal to a map with [1->a] in it, and a map with [1->a] isn't equal to a map with [1->a; 2->b].
This has nothing to do with creating a new object, and the reference to the old map does not change. If you call System.identityHashCode(map) instead of map.hashCode() you'll see the object reference does not change no matter how many times you call put on it.
Does putting additional stuff in the TreeMap change something in the equals() method of TreeMap?
Yes, of course, since the contract of equals, for a Map, 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())
So, every entry of the map is used to check for equality, and is thus also used to compute the hashCode. And adding an entry thus modified the hashCode.
You're expecting hashCode to be some sort of immutable identifier for an object. It's not. Not at all.
Does putting additional stuff in the TreeMap change something in the equals() method of TreeMap? Do I have to somehow override equals() and hashCode()?
Why wouldn't it? A new TreeMap is empty. So with your reasoning, if its equals() and hashCode() methods didn't adjust according to the contents of the TreeMap all instances of TreeMap (that start from empty) would have the same hashcode, because it would be the same one computed on creation with no entries inside it, irrespective what is added afterwards.
The equals() and hashCode() functions for most Collections adjust according to their contents. This way a Set or List of elements could be compared to another instance of a Set or List of elements, and equals() would return true if they contain the same elements (in the same order in case of List). Similarly for Map the internal equals() implementation is making sure to check whatever is considered equivalent for that Map implementation.
I was checking headMap method of TreeMap which returns a portion of Map whose keys are strictly less than toKey. So I was expecting output to be B, C but it returns only B. Here is a thing I did weird I changed compareTo method like this return this.priority > o.priority ? 1 : -1; then it started returning C, B which is I was expecting. I am sure this is not correct but how can I get both B, C which has lower priority than A. Where I am getting it wrong. Thanks.
NavigableMap<PolicyTypePriorityWrapper, String> treeMap = new TreeMap();
PolicyTypePriorityWrapper a = new PolicyTypePriorityWrapper("A", 2);
PolicyTypePriorityWrapper b = new PolicyTypePriorityWrapper("B", 1);
PolicyTypePriorityWrapper c = new PolicyTypePriorityWrapper("C", 1);
treeMap.put(a, "A");
treeMap.put(b, "B");
treeMap.put(c, "C");
NavigableMap<PolicyTypePriorityWrapper, String> map = treeMap.headMap(a, false);
Set<PolicyTypePriorityWrapper> policyTypePriorityWrappers = map.keySet();
for (PolicyTypePriorityWrapper pol: policyTypePriorityWrappers) {
System.out.println(pol.getPolicyType());
}
PolicyTypePriorityWrapper.java
class PolicyTypePriorityWrapper implements Comparable<PolicyTypePriorityWrapper> {
private String policyType;
private int priority;
public PolicyTypePriorityWrapper(final String policyType, final int priority) {
this.policyType = policyType;
this.priority = priority;
}
public String getPolicyType() {
return this.policyType;
}
public int getPriority() {
return this.priority;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PolicyTypePriorityWrapper that = (PolicyTypePriorityWrapper) o;
if (priority != that.priority) return false;
return policyType.equals(that.policyType);
}
#Override
public int hashCode() {
int result = policyType.hashCode();
result = 31 * result + priority;
return result;
}
#Override
public int compareTo(final PolicyTypePriorityWrapper o) {
return Integer.compare(this.priority, o.priority);
}
}
That's because you are not following JDK documentation guidelines, from Comprarable:
It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.
As you can see you have circumstances in which a.compareTo(b) == 0 but !a.equals(b). Both "B", 1 and "C", 1 are considered equal for the TreeMap:
Note that the ordering maintained by a tree map, like any sorted map, and whether or not an explicit comparator is provided, must be consistent with equals if this sorted map is to correctly implement the Map interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Map interface is defined in terms of the equals operation, but a sorted map performs all key comparisons using its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of the sorted map, equal. The behavior of a sorted map is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Map interface.
For example, if one adds two keys a and b such that (!a.equals(b) && a.compareTo(b) == 0) to a sorted set that does not use an explicit comparator, the second add operation returns false (and the size of the sorted set does not increase) because a and b are equivalent from the sorted set's perspective.
So what happens is that you compareTo is not able to distinguish two elements with same priority but different type, but since a TreeMap is using ONLY that method to decide if two elments are equal then you are not adding them both to the map in the first place.
Did you try if treeMap.size() == 3? My guess is that it's 2 in the first place.
The new PolicyTypePriorityWrapper("B", 1) is not qualified because it does it even not make it into the treeMap.
Why? Because the keys are the PolicyTypePriorityWrapper objects which are compared according to their integer priority value. Since b and c have the same priority, only the last one is saved to the treeMap. Compared a, b and c has a lower priority than a and equal to b. The key remains and the value is replaced. So in the map appears an entry PolicyTypePriorityWrapper b with the newly replaced value C.
It's the behavior of Map::put(K key, V value) method.
If the map previously contained a mapping for the key, the old value is replaced by the specified value.
Now the NavigableMap::headMap(K toKey, boolean inclusive) which returns a view of the portion of this map whose keys are less than (or equal to, if inclusive is true) toKey (taken from the documentation). The result is obvious. Only a and b stay in the treeMap, so the a is filtered out since it has smaller priority to b and only b is qualified to be returned.
I have a data structure containing a list of objects, like this:
class A {
private List<Object> list;
}
How to properly define a hash function for the list, assuming each element of the list has a correct hashCode()?
If the actual List implementation is fully conformant to the interface, the provided hashCode implementation should be sufficient:
Returns the hash code value for this list. The hash code of a list is defined to be the result of the following calculation:
hashCode = 1;
Iterator i = list.iterator();
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
(List documentation)
The List interface requires conforming implementations to provide equals based on the elements of the list. Thus, they had to specify the hashCode algorithm explicitely
Why do you want to define hashCode for your list, when it already has it implemented (along with equals)?
(Provided it is java.util.List of course - however if not, the link above shows you the exact implementation you can use for your own list type.)
The hash code of a list is defined by the List interface of List. This can be used as part of your object's hash code, though there are a couple of cases where you might not want to use it - if the elements of your list have an expensive hash code function, or if the list can hold a reference to the object, and you would then get a stack overflow if the list's algorithm was used. In that case, just use the length of the list or another hash value.
In the Java library, List implementations (LinkedList, ArrayList) use the default hashCode implementation provided by AbstractList. which is defined as:
int hashCode = 1;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
Any specific reason why you just wouldn't do:
Arrays.hashCode(<cast list to array>);
Something like:
Arrays.hashCode((String []) myList.toArray());
Maybe the question should have been "how to compute a hashcode of an object containing a list".
class A {
private List<Object> list;
#Override
public int hashCode() {
return list.hashCode();
}
// ... don't forget to implement custom equals ...
}
public final Comparator<String> ID_IGN_CASE_COMP = new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
};
private Map< String, Animal > _animals = new TreeMap< String, Animal >(ID_IGN_CASE_COMP);
My problem is, how to use method get(id) ignoring the given comparator. I want the map to be order by Case Insensitive but, I want it to be case sensitive when I fetch the values by a given key.
I think the answer is easy. Implement your own comparator that does a case insensitive sort but does NOT return 0 for "A" and "a"... sort them too.
The issue is that your comparator returns 0 for the compare( "A", "a" ) case which means it is the same key as far as the map is concerned.
Use a comparator like:
public final Comparator<String> ID_IGN_CASE_COMP = new Comparator<String>() {
public int compare(String s1, String s2) {
int result = s1.compareToIgnoreCase(s2);
if( result == 0 )
result = s1.compareTo(s2);
return result;
}
};
Then all keys will go in regardless of case and "a" and "A" will still be sorted together.
In other words, get("a") will give you a different value from get("A")... and they will both show up in keySet() iterators. They will just be sorted together.
In a TreeMap, adding two keys a and b (in that order) so that compare(a, b) returns 0 will result in that the latest added entry (b) will overwrite the first one (a).
In your case, this means that there will never be any use for case insensitive get(id).
quoting http://java.sun.com/javase/6/docs/api/java/util/TreeMap.html
Note that the ordering maintained by a sorted map (whether or not an explicit comparator is provided) must be consistent with equals if this sorted map is to correctly implement the Map interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Map interface is defined in terms of the equals operation, but a map performs all key comparisons using its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of the sorted map, equal. The behavior of a sorted map is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Map interface.
This is probably not what you want.
If the map is comparably small and you don't need to fetch the sorted entries very many times, a solution is to use a HashMap (or a TreeMap without explicitly setting the comparator), and sort the entries case-insensitively when you need them ordered.
You'll have to use two separate TreeMaps for that, with the same contents but different comparators.
maybe it'll do the job:
new Comparator<String>(){
public int compare(String s1, String s2)
{
String s1n = s1.toLowerCase();
String s2n = s2.toLowerCase();
if(s1n.equals(s2n))
{
return s1.compareTo(s2);
}
return s1n.compareTo(s2n);
}
};
}
you need a multimap: each entry of this multimap keeps the case insensitive keys and aanother map with the original keys as value.
There are many freely usable implementations of multimaps such as Common Collections, Google Collections, etc
In addition to all the other answers and agreeing, that it is impossible to have a single TreeMap structure with different comparators:
From your question I understand that you have two requirements: the data model shall be case sensitive (you want the case sensitive values when you use get()), the presenter shall be case insensitive (you want an case sensitive ordering, presentation is just an assumption).
Let's assume, we populate the Map with the mappings (aa,obj1), (aA,obj2), (Aa,obj3), (AA,obj4). The iterator will provides the values in the order: (obj4, obj3, obj2, obj1)(*). Now which order do you expect if the map was ordered case-insensitive? All four keys would be equal and the order undefined. Or are you looking for a solution that would resolve the collection {obj1, obj2, obj3, obj4} for the key 'AA'? But that's a different approach.
SO encourages the community to be honest: therefore my advice at this point is to look at your requirement again :)
(*) not tested, assumed that 'A' < 'a' = true.
Use floorEntry and then higherEntry in a loop to find the entries case-insensitively; stop when you find the exact key match.