I have innumerable maps with custom keys and comparators. I have noticed that the when I create a map using code like
var map = TreeMap<>( someCustomComparator );
And then later I create an immutable (and small and fast) copy of it using code like:
map = Map.copyOf( map );
Then map.get( similarObject ) then fails to retrieve someObject, even when someObject and similarObject compare equal ("have the same equivalence class") under the comparator someCustomComparator.
Debugging into the API I find that Map.copyOf returns a map implementation that uses Object::equals to compare keys, i.e. it does not use the comparator used to construct the original map (in my example this would be someCustomComparator). Obviously when someObject and similarObject are not the same object but have the same equivalence class under someCustomComparator but Object::equals is not overridden, this then yields the bizarre result that
map.get( similarObject ) ==> someObject
before the map = Map.copyOf( map ) instruction, and
map.get( similarObject ) ==> null
after the map = Map.copyOf( map ) instruction. Is this expected behaviour I have to live with or should I report a Java defect ?
(Note that the class of some/similarObject also implements comparable, and that is also ignored by the Map.copyOf implementation.)
(I presume this behaviour is common across all the collection copyOf implementations.)
The specification for the copyOf method states
Returns an unmodifiable Map containing the entries of the given Map.
(emphasis mine)
Which means only the entries get copied, nothing else. Hence, it is expected behavior, not a bug.
On the other hand, as suggested in the comments, the documentation for the constructor of the TreeMap class says
Constructs a new tree map containing the same mappings and using the same ordering as the specified sorted map. This method runs in linear time.
Parameters:
m - the sorted map whose mappings are to be placed in this map, and whose comparator is to be used to sort this map
(again, emphasis mine)
In addition to what Federico said in his answer, the Javadoc of Map also says this in the "Unmodifiable Maps" section:
The iteration order of mappings is unspecified and is subject to change.
If you need an unmodifiable map that uses the same order, wrap the map using Collections.unmodifiableMap. If you want a copy, just create a new TreeMap as Federico said:
private static <K, V> SortedMap<K, V> copyOf(SortedMap<K, V> map) {
return Collections.unmodifiableSortedMap(new TreeMap<>(map));
}
In the API specification of Map, Map.get(Object key) states:
"More formally, if this map contains a mapping from a key k to a value v such that Objects.equals(key, k), then this method returns v; otherwise it returns null. (There can be at most one such mapping.)"
And the API specification of SortedMap states:
"All keys inserted into a sorted map must implement the Comparable interface (or be accepted by the specified comparator)" and
"Note that the ordering maintained by a sorted map (whether or not an explicit comparator is provided) must be consistent with equals if the sorted map is to correctly implement the Map interface."
It is this latter paragraph that I had forgotten when I posted this question. Hence, the above behaviour mentioned in the question is expected, even if it is bizarre. So To get consistent behaviour between Map and SortedMap Object::equals must be overridden to be consistent with the Comparable or Comparator, whichever is being used.
This is quite a serious requirement / restriction because it means that you should only ever use maps with comparators in combination with keys where the key's implementation overrides Object::equals to be consistent with the bespoke comparator's version of equals. I.e. in principle for every bespoke comparator there should be a corresponding bespoke key. I think this point is easily missed; I certainly missed it.
A corollary of this requirement is that objects that are not specifically designed as bespoke keys for some bespoke comparator cannot in general be used as keys for a map with a bespoke comparator (something/anything inheriting SortedMap) since in general the map will break the contract for Map. I find this whole situation problematic. I think it would be more consistent to utilise the following comparison hierarchy for all maps (including Map):
If present, Comparator<T>, else
if defined for the supplied key type, Comparable<T>, else
if overridden for the supplied key type, Object::equals, else
use the default Object::equals, i.e. ==.
I.e. if a comparator has been supplied to a map, use it, otherwise if the key is comparable, use that, otherwise use the key's equal(Object) method, the default implementation of which is ==. Implementations of Map that do not permit bespoke comparators (effectively anything that is NOT a SortedMap) would need to accept comparators (and use Comparable<T> where available when a comparator is not supplied) for the purpose of defining equivalence classes (not for ordering). For this purpose it might be useful to define the concept of an "Equalator<T>", or "Equivalencer<T>" to provide the necessary bespoke definition of equals (equivalence) for keys of type T present in some Map. This avoids the need to define a sort order for maps that don't require it / don't accept bespoke sort orders. Comparators could be modified to inherit Equalator<T> to provide explicit equivalence as well as a sort order (Comparator<T> in general implies an implicit definition of equivalence anyway).
This change to the Map API specification would avoid the need to define a bespoke key for every comparator for SortedMaps, and allow the use of general objects as keys without breaking the contract for Map. It would also mean that the same object type could be used as a key in different Maps while utilising different definitions of equivalence, something which is not easily possible at the moment since Map is restricted to using Object::equals to define equivalence.
I don't have a use case for different equivalence classes with unsorted maps, but I have a large number of use cases for bespoke comparators, sometimes with and sometimes without bespoke keys, and it is problematic that I need to define both a comparator for the map and a consistent Object::equals in the associated key. The comparator should, in my opinion, be sufficient. Forgetting to define, or making a definition of Object::equals inconsistent with a map's comparator leads all too easily to weird defects that can manifest far away from the cause and require API debugging to understand.
Related
I was just wondering if there is any consideration to have in account when saving our own objects in a TreeMap. Something similar when we save our own objects as keys in a hashmap that we need to override equals and hashcode method to be able to retrieve them later. In a treemap there is no hash, a black red algorith is used, but I don't know if there is something special to do.
If so, could you tell me if there is something to have in account?
Thanks
The javadoc says:
The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time
So you need to implement a natural ordering correctly, or implement a Comparator correctly.
It also says:
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.
So, if you want to obey the general contract of Map (and you should, generally), the compareTo() method must be consistent with equals(), which means that you need to correctly implement an equals() method, and transitively, a hashCode() method, and that you must make sure that a.equals(b) iff e.compareTo(b) == 0.
Most of the times, people screw up because they implement a compareTo/compare method that returns 0 for two objects, and still expect these two objects to be considered different by the map.
Apart from equals and compareTo transitivity there's one more thing that's terribly important.
Your keys, or at least the fields you're using for comparison, should be immutable.
And you can actually use anything as a key for a TreeMap as long as you provide custom Comparator in its constructor.
I am trying to create a method that sorts a List by making:
private List<Processor> getByPriority(){
return processors.stream().sorted( new ProcessorComparator() ).collect( Collectors.toList() );
}
But I read in the Comprator javadoc that compare to needs to be a total ordering relation. That is, no two comparator may have the same priority unless they are equal. This might not be the case.
I was trying this simple comparator:
public class ProcessorComparator implements Comparator<TTYMessageProcessor<?>>{
#Override
public int compare( Processor processor1 , Processor processor2 ) {
return processor1.getPriority() - processor2.getPriority();
}
}
Of course I could make the Processor Comparable but I would like to avoid modifications to all the Proccessors. Isn't there any way to sort them with streams? As alternatives I could write my own method or create a more complex comparator but I am surprised of the lack of a more elegant solution.
Reading the references the elements of the original stream are preserved:
Returns a stream consisting of the elements of this stream, sorted according to the provided Comparator.
No elements will be evicted, deleted or duplicated. The same elements come out of the sort as go in, just re-ordered.
Edit: the docs also state for Comparator.compare
It is generally the case, but not strictly required that (compare(x,
y)==0) == (x.equals(y)). Generally speaking, any comparator that
violates this condition should clearly indicate this fact. The
recommended language is "Note: this comparator imposes orderings that
are inconsistent with equals."
This may introduce confusion about equals when used in maps or sets:
Caution should be exercised when using a comparator capable of
imposing an ordering inconsistent with equals to order a sorted set
(or sorted map). Suppose a sorted set (or sorted map) with an explicit
comparator c is used with elements (or keys) drawn from a set S. If
the ordering imposed by c on S is inconsistent with equals, the sorted
set (or sorted map) will behave "strangely." In particular the sorted
set (or sorted map) will violate the general contract for set (or
map), which is defined in terms of equals.
The confusion is lifted if you think about Comparator as an abstraction of the key-value pair: you wouldn't expect two pairs to be equal in case their keys were equal. It just means that some property of those values (i.e. their keys) is considered alike. If you wanted an object to be Comparable in a manner consistent with equals best implement the equally named interface Comparable.
I have an object, Foo which inherits the default equals method from Object, and I don't want to override this because reference equality is the identity relation that I would like to use.
I now have a specific situation in which I would now like to compare these objects according to a specific field. I'd like to write a comparator, FooValueComparator, to perform this comparison. However, if my FooValueComparator returns 0 whenever two objects have the same value for this particular field, then it is incompatible with the equals method inherited from Object, along with all the problems that entails.
What I would like to do would be to have FooValueComparator compare the two objects first on their field value, and then on their references. Is this possible? What pitfalls might that entail (eg. memory locations being changed causing the relative order of Foo objects to change)?
The reason I would like my comparator to be compatible with equals is because I would like to have the option of applying it to SortedSet collections of Foo objects. I don't want a SortedSet to reject a Foo that I try to add just because it already contains a different object having the same value.
This is described in the documentation of Comparator:
The ordering imposed by a comparator c on a set of elements S is said to be consistent with equals if and only if c.compare(e1, e2)==0 has the same boolean value as e1.equals(e2) for every e1 and e2 in S.
Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map). Suppose a sorted set (or sorted map) with an explicit comparator c is used with elements (or keys) drawn from a set S. If the ordering imposed by c on S is inconsistent with equals, the sorted set (or sorted map) will behave "strangely." In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals.
It short, if the implementation of Comparator is not consistent with equals method, then you should know what you're doing and you're responsible of the side effects of this design, but it's not an imposition to make the implementation consistent to Object#equals. Still, take into account that it is preferable to do it in order to not cause confusion for future coders that will maintain the system. Similar concept applies when implementing Comparable.
An example of this in the JDK may be found in BigDecimal#compareTo, which explicitly states in javadoc that this method is not consistent with BigDecimal#equals.
If your intention is to use a SortedSet<YourClass> then probably you're using the wrong approach. I would recommend using a SortedMap<TypeOfYourField, Collection<YourClass>> (or SortedMap<TypeOfYourField, YourClass>, in case there are no equals elements for the same key) instead. It may be more work to do, but it provides you more control of the data stored/retrieved in/from the structure.
You may have several comparators for a given class, i.e each per different field. In that case equals can not be reused. Therefore the answer is not necessarily. You should make them consistence however if your collection is stored in a sorted (map or tree) and the comperator is used to determined element position in that collection.
See documentation for details.
I need to do this for a unit test. The method being tested returns an ImmutbaleMap and I need to be able to compare it with one that I already have. One way is to get key sets for both(keySets()), run through them and compare the values returned from both maps for those keys. However that to me seems a little inefficient. Is there a better/preferred way to do this ?
If both the keys and the values implement equals() correctly, you can simply use Map.equals():
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()). This ensures that the equals method works properly across different implementations of the Map interface.
If they don't, I doubt you'll find a one-liner that works out of the box. I expect you'd have to implement the comparison yourself. It's not hard to do:
If the symmetric difference between the two key sets is not empty, you're done.
Otherwise, iterate over one map, looking up the same key in the other and comparing the values (using whatever comparison method is appropriate).
This can be easily encapsulated into a helper function, perhaps parameterised by the value comparator.
Complement to #NPE's answer...
Since your values do not implement .equals()/.hashCode() correctly, a simple equals on maps will not work; but you use Guava; theefore you have the option of implementing an Equivalence.
This means, if the class of your values is Foo:
you'll need to implement an Equivalence<Foo>:
your map will have to be a Map<X, Equivalence.Wrapper<Foo>>.
With this, you'll be able to use Map's .equals().
You'll have to add values using Equivalence's .wrap() method. See here for an example of an Equivalence implementation.
Another choice would be to use plain Map, write impmementation of Equivalence and use followingdifference method from Maps class
MapDifference<K,V> difference(Map<? extends K,? extends V> left,
Map<? extends K,? extends V> right,
Equivalence<? super V> valueEquivalence)
I would prefer this way as it will not alter the Map types (won't do it just to calculate difference or to check equality).
Why should map1.entrySet().equals(map2.entrySet()) not work?
The EntrySet.equals() method refers to the Map.contains() method which should work for your values whether they implement equals() or not (in this case you probably have an IdentityHashMap underlying).
I need to create a Set of objects. The concern is I do not want to base the hashing or the equality on the objects' hashCode and equals implementation. Instead, I want the hash code and equality to be based only on each object's reference identity (i.e.: the value of the reference pointer).
I'm not sure how to do this in Java.
The reasoning behind this is my objects do not reliably implement equals or hashCode, and in this case reference identity is good enough.
I guess that java.util.IdentityHashMap is what you're looking for (note, there's no IdentityHashSet). Lookup the API documentation:
This class implements the Map interface with a hash table, using reference-equality in place of object-equality when comparing keys (and values). In other words, in an IdentityHashMap, two keys k1 and k2 are considered equal if and only if (k1==k2). (In normal Map implementations (like HashMap) two keys k1 and k2 are considered equal if and only if (k1==null ? k2==null : k1.equals(k2)).)
This class is not a general-purpose Map implementation! While this class implements the Map interface, it intentionally violates Map's general contract, which mandates the use of the equals method when comparing objects. This class is designed for use only in the rare cases wherein reference-equality semantics are required.
edit: See Joachim Sauer's comment below, it's really easy to make a Set based on a certain Map. You'd need to do something like this:
Set<E> mySet = Collections.newSetFromMap(new IdentityHashMap<E, Boolean>());
You could wrap your objects into a wrapper class which could then implement hashcode and equals based simply on the object's identity.
You can extend HashSet (or actually - AbstractSet) , and back it with IdentityHashMap which uses System.identityHashCode(object) instead of obj.hashCode().
You can simply google for IdentityHashSet, there are some implementations already. Or use Collections.newSetFromMap(..) as suggested by Joachim Sauer.
This of course should be done only if you are not in "possession" of your objects' classes. Otherwise just fix their hashCode()