There is a Java bean object which has implemented equals function based on certain criteria (Criteria A). I have a requirement to identify unique objects based on another criteria (Criteria B). Since the equals function uses criteria A, I can not use HashSet. So I thought of using TreeSet with my custom Comparator which is based on criteria B. My question is, Is it allowed to do like this? Any issues with this approach?
Thank you.
Here is some guide from Oracle Java:
Note that the ordering maintained by a
set (whether or not an explicit
comparator is provided) must be
consistent with equals if it is to
correctly implement the Set interface.
(See Comparable or Comparator for a
precise definition of consistent with
equals.) This is so because the Set
interface is defined in terms of the
equals operation, but a TreeSet
instance 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 set, equal. The
behavior of a set is well-defined even
if its ordering is inconsistent with
equals; it just fails to obey the
general contract of the Set interface.
I think in terms of technical, no, you don't have any problems. But, in terms of coding, readability and maintainability, you have to be careful, because other people can misuse or misunderstand what you are doing
If you perform search often and add elements rarely, consider keeping them in a List sorted by Criteria B and using Collections.binarySearch.
You can wrap them:
class BeanWrapper {
...
public boolean equals(Object other) {
return myBean.critB.equals(((Bean)other).critB);
}
}
And put them in the set like that.
Related
I have a data structure which takes an optional Comparator to customise ordering (in this case it is a TreeSet but really it doesn't matter, I can swap out for a PriorityQueue without breaking my code). At present, it is ordered by a field price on the object which the structure is storing.
When 2 objects have the same price, I want timestamp to be the tie-breaker, timestamp being System.CurrentTime. To specify this in the comparator I have to use:
if (Object1.getPrice == Object2.getPrice && Object1.Timestamp > Object2.Timestamp) return 1
The problem is that this breaks the equals case when I do TreeSet.floor() or TreeSet.ceiling() - the method no longer recognises that 2 objects are of equal price but will still recognise if the price is higher/lower. How do I mitigate this?
With the Comparator you decide what is the order of the set (actually as well their "uniqueness"!).
So you have to decide if the objects are equal:
if the Price is the same
if the Price and Timestamp is the same
maybe using different data-strucure for this different usages would be an option or a different that could cover both. Depending on the other requirements and boundaries of your code.
Take a look here: https://docs.oracle.com/javase/7/docs/api/java/util/TreeSet.html
A NavigableSet implementation based on a TreeMap. The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used.
So you provide the Comparator.
Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal.
So if there is already a equals object in the Set (consulting the comparator) it want be added again as this would be placed at the very same position in the Set according to the order (if you don't like that don't use a Set). They should be implemented consistent (even tough it is not forced by the compiler). So instead of using direct equals it used the comparator.
Internally the comparator could use the equals operator of the objects (inside the if statement like if(obj1.equals(obj2)){return 0;}).
But how to implement the equals - internally it would rely on some property comparison (like you do now in the comparator).
If you don't like that for your data structure maybe don't use a (Tree)Set.
Depending on the needs a Map or a List might be fine.
Choosing the right Collection:
http://www.javapractices.com/topic/TopicAction.do?Id=65
For the difference between == and equals:
== is comparing the references, lets say the pointer to the container, if true it is the same instance.
equals is comparing the object, lets say the content of the object container, if true it holds same value(s) (in its properties)
But out there are probably millions of exhausting articles on that.
Note: equals of an Object should be implemented consistent with the hashCode() method. And for a (Tree)Set the comparator should be implemented consistent with equals (maybe use something like if(obj1.equals(obj2)){return 0;}). Furthermore, other Set implementations use the hasCode() for that.
In Java, when should I implement Comparable<Something> versus implementing the equals method? I understand every time I implement equals I also have to implement hash code.
EDIT
Based on answers I am getting below:
Is it safe to say that if I implement Comparable then I don't need to implement equals and hashCode? As in: whatever I can accomplish with equal is already included in compareTo? For an example, I want to be able to compare two BSTs for equality. Implementing a hashCode for that seems daunting; so would comparable be sufficient?
If you only ever need to compare them for equality (or put them in a HashMap or HashSet which is effectively the same) you only need to implement equals and hashcode.
If your objects have an implicit order and you indend to sort them (or put them in a TreeMap or TreeSet which is effectively sorting) then you must implement Comparable or provide a Comparator.
Comparable is typically used for ordering items (like sorting) and equals is used for checking if two items are equal. You could use comparable to check for equality but it doesn't have to be. From the docs, "It is strongly recommended (though not required) that natural orderings be consistent with equals"
equals (and hashCode) are used for equality tests. The Comparable interface can be used for equality checks too, but it is in practice used for sorting elements or comparing their order.
That is, equals deals only with equality (similar to == and !=), while compareTo allows you to check many different types of inequality (similar to <, <=, ==, >=, > and !=). Therefore, if your class has a partial or total ordering of some kind, you may want to implement Comparable.
The relationship between compareTo and equals is briefly mentioned in the javadocs for Comparable:
It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)). Generally speaking, any class that implements the Comparable interface and violates this condition should clearly indicate this fact. The recommended language is "Note: this class has a natural ordering that is inconsistent with equals."
Comparable is used by TreeSet to compare and sort anything you insert into it, and it is used by List#sort(null) to sort a list. It will be used in other places too, but those are the first that come to mind.
You should consider when you will use the object.
If for example in TreeMap and TreeSet, then you'll need Comparable. Also any case, when you will have to sort elements(especially sorted collections), implementing Comparable is mandatory. Overriding equals and hashcode will be needed in a very large amount of cases, most of them.
As per the javadocs:
This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's compareTo method is referred to as its natural comparison method.
Lists (and arrays) of objects that implement this interface can be sorted automatically by Collections.sort (and Arrays.sort). Objects that implement this interface can be used as keys in a sorted map or as elements in a sorted set, without the need to specify a comparator.
The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C. Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false
In short, equals() and hashcode() are for comparing equality whereas Comparable is for sorting
If you update the equals method, you should always update hashCode or you will be setting a boobytrap for yourself or others when they try to use it in a HashMap or HasSet or similar collections (very commonly used).
Comparable is required only when the interfaces you are working with require it. You can implement it for use with sorting and sorted collections but it's not actually required in most cases. An alternate approach is to pass in a Comparator to the sort or collection constructor. For example the String class has a case insensitive Comparator that is very useful and eliminates the need to create your own String class (i.e. String cannot be extended.)
TreeSet has a constructor that takes a comparator, meaning even if the objects you store aren't Comparable objects by themselves, you can provide a custom comparator.
Is there an analogous implementation of a nonordered set? (e.g. an alternative to HashSet<T> that takes a "hasher" object that calculates equals() and hashCode() for objects T that may be different from the objects' own implementations?)
C++ std::hash_set gives you this, just wondering if there's something for Java.
Edit: #Max brings up a good technical point about equals() -- fair enough; and it's true for TreeMap and HashMap keys via Map.containsKey(). But are there other well-known data structures out there that allow organization by custom hashers?
No, having a "hasher" object is not supported by the Collections specifications. You can certainly implement your own collection that supports this but another way to do this is to consider the Hasher to be a wrapping object that you store in your HashSet instead.
Set<HasherWrapper<Foo>> set = new HashSet<HasherWrapper<Foo>>();
set.add(new HasherWrapper(foo));
...
The wrapper class would then look something like:
private class HasherWrapper<T> {
T wrappedObject;
public HasherWrapper(T wrappedObject) {
this.wrappedObject = wrappedObject;
}
#Override
public int hashCode() {
// special hash code calculations go here
}
#Override
public boolean equals(Object obj) {
// special equals code calculations go here
}
}
There is no such implementation in the standard library, but it doesn't prevent you from rolling your own. This is something i've often wanted to have myself.
See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4771660 for the reason:
We wanted to avoid the complexity. We seriously entertained this notion
at the time the collections framework was designed, but rejected it. The
power-to-weight ration seemed to low. We felt that equals was what you
wanted 95% of the time; ==, 4%; and something else 1%. Writing sensible
contracts for bulk operations when is very tricky when equality predicates
differ.
No, there is not and there can not be by specification. Moreover, you misunderstood the way TreeSet uses it's Comparator.
From TreeSet Javadoc:
Note that the ordering maintained by a set (whether or not an explicit
comparator is provided) must be consistent with equals if it is to
correctly implement the Set interface. (See Comparable or Comparator
for a precise definition of consistent with equals.) This is so
because the Set interface is defined in terms of the equals operation,
but a TreeSet instance performs all element comparisons using its
compareTo (or compare) method, so two elements that are deemed equal
by this method are, from the standpoint of the set, equal. The
behavior of a set is well-defined even if its ordering is inconsistent
with equals; it just fails to obey the general contract of the Set
interface.
From Comparable javadoc:
The natural ordering for a class C is said to be consistent with
equals if and only if e1.compareTo(e2) == 0 has the same boolean value
as e1.equals(e2) for every e1 and e2 of class C. Note that null is not
an instance of any class, and e.compareTo(null) should throw a
NullPointerException even though e.equals(null) returns false.
From Collection javadoc:
boolean contains(Object o)
Returns true if this collection contains
the specified element. More formally, returns true if and only if this
collection contains at least one element e such that (o==null ?
e==null : o.equals(e)).
Therefore, by specification there can not be any kind of class that implements Collection<E> interface and fully depend on some external Comparator-style object to insert objects. All Collections should use equals method of an Object class to verify if the object is already inserted.
There is definitely nothing like that, hashcode() and equals() are defining attributes of an object and should not be changed. They define what makes an object equal to each other and this shouldn't be different from one set to another. The only way to do what you are talking about is to subclass the object and write a new hashcode() and equals() and this would only really make sense to do if the subclass had a defining variable that should be added in addition to the super class' hashcode() and equals(). I know this may not be what you are aiming for but I hope this helps. If you explain your reasoning more for wanting this then it might help to find a better solution if there exists one.
Java has a Comparator<T> for providing comparison of objects external to the class itself, to allow for multiple/alternate methods of doing ordered comparisons.
But the only standard way of doing unordered comparisons is to override equals() within a class.
What should I do when I want to provide multiple/alternate unordered comparisons external to a class? (Obvious use case is partitioning a collection into equivalence classes based on particular properties.)
Assuming the end use is for unordered checking (e.g. not for sorting or indexing), is it ever OK to implement Comparator<T> that just checks for equality, returning 0 if two objects are equal, and a value != 0 when two objects are unequal? (note: the only reason I don't jump on this solution, is that technically it can break the contract for Comparator by not providing a relation that satisfies transitivity and symmetry.)
It seems like there should have been an EqualsComparator<T> standard class or something.
(Does Guava handle anything like this?)
Yes, Guava has the Equivalence interface and the Equivalences class (Removed in Guava release 14.0).
(And yes, it's something which is very useful and sadly lacking in Java. We really should have options around this for HashMap, HashSet etc...)
While Comparator<T> may be okay in some situations, it doesn't provide the hashCode method which would be important for hash-based collections.
I'd like to have a SortedSet of Collections (Sets themselves, in this case, but not necessarily in general), that sorts by Collection size. This seems to violate the proscription to have the Comparator be consistent with equals() - i.e., two collections may be unequal (by having different elements), but compare to the same value (because they have the same number of elements).
Notionally, I could also put into the Comparator ways to sort sets of equal sizes, but the use of the sort wouldn't take advantage of that, and there's not really a useful + intuitive way to compare the Collections of equal size (at least, in my particular case), so that seems like a waste.
Does this case of inconsistency seem like a problem?
SortedSet interface extends core Set and thus should conform to the contract outlined in Set specification.
The only possible way to achieve that is to have your element's equal() method behavior be consistent with your Comparator - the reason for that is that core Set operates based on equality whereas SortedSet operates based on comparison.
For example, add() method defined in core Set interface specifies that you can't add an element to the set if there already is an element whose equal() method would return true with this new element as argument. Well, SortedSet doesn't use equal(), it uses compareTo(). So if your compareTo() returns false your element WILL be added even if equals() were to return true, thus breaking the Set contract.
None of this is a practical problem per se, however. SortedSet behavior is always consistent, even if compare() vs equals() are not.
As ChssPly76 wrote in a comment, you can use hashCode to decide the compareTo call in the case where two Collections have the same size but are not equal. This works fine, except in the rare case where you have two Collections with the same size, are not equal, but have the same hashCode. Admittedly, the chances of that happening are pretty small, but it is conceivable. If you want to be really careful, instead of hashCode, use System.identityHashCode instead. This should give you a unique number for each Collection, and you shouldn't get collisions.
At the end of the day, this gives you the functionality of having the Collections in the Set sorted by size, with arbitrary ordering in the case of two Collections with matching size. If this is all you need, it's not much slower than the usual comparison would be. If you need the ordering to be consistent between different JVM instances, this won't work and you'll have to do it some other way.
pseudocode:
if (a.equals(b)) {
return 0;
} else if (a.size() > b.size()) {
return 1;
} else if (b.size() > a.size()) {
return -1;
} else {
return System.identityHashCode(a) > System.identityHashCode(b) ? 1 : -1;
}
This seems to violate the proscription
to have the Comparator be consistent
with equals() - i.e., two collections
may be unequal (by having different
elements), but compare to the same
value (because they have the same
number of elements).
There is no requirement, either stated (in the Javadoc) or implied, that a Comparator be consistent with an object's implementation of boolean equals(Object).
Note that Comparable and Comparator are distinct interfaces with different purposes. Comparable is used to define a 'natural' order for a class. In that context, it would be a bad idea for equals and compateTo to be inconsistent. By contrast, a Comparator is used when you want to use a different order to the natural order of a class.
EDIT: Here's the complete paragraph from the Javadoc for SortedSet.
Note that the ordering maintained by a
sorted set (whether or not an explicit
comparator is provided) must be
consistent with equals if the sorted
set is to correctly implement the Set
interface. (See the Comparable
interface or Comparator interface for
a precise definition of consistent
with equals.) This is so because the
Set interface is defined in terms of
the equals operation, but a sorted
set performs all element comparisons
using its compareTo (or compare)
method, so two elements that are
deemed equal by this method are, from
the standpoint of the sorted set,
equal. The behavior of a sorted set is
well-defined even if its ordering is
inconsistent with equals; it just
fails to obey the general contract of
the Set interface.
I have highlighted the final sentence. The point is that such a SortedSet will work as you would most likely expect, but the behavior of some operations won't exactly match the Set specification ... because the specification defines their behavior in terms of the equals method.
So in fact, there is a stated requirement for consistency (my mistake), but the consequences of ignoring it are not as bad as you might think. Of course, it is up to decide if you should do that. In my estimation, it should be OK, provided that you comment the code thoroughly and make sure that the SortedSet does not 'leak'.
However, it is not clear to me that a Comparator for collections that only looks at an collections "size" is going to work ... from a semantic perspective. I mean, do you really want to say that all collections with (say) 2 elements are equal? This will mean that your set can only ever contain one collection of any given size ...
There is no reason why a Comparator should return the same results as equals(). In fact, the Comparator API was introduced because equals() just isn't enough: If you want to sort a collection, you must know whether two elements are lesser or greater.
It's a little bit odd that SortedSet as a part of the standard API breaks the contract defined in the Set interface and uses the Comparator to define equality instead of the equals method, but that's how it is.
If your actual problem is to sort a collection of collections according to the containted collections' size, you are better of with a List, which you can sort using Collections.sort(List, Comparator>);