There is a java bean Car that might contains two values: model and price.
Now suppose I override equals() and hashcode() checking only for model in that way:
public boolean equals(Object o) {
return this.model.equals(o.model);
}
public int hashCode() {
return model.hashCode();
}
This permit me to check if an arraylist already contain an item Car of the same model (and doesn't matter the price), in that way:
List<Car> car = new ArrayList<Car>();
car.add(new Car("carA",100f));
car.add(new Car("carB",101f));
car.add(new Car("carC",110f));
System.out.println(a.contains(new Car("carB",111f)));
It returns TRUE. That's fine, because the car already exist!
But now I decide that the ArrayList is not good, because I want to maintain the items ordered, so I substitute it with a TreeSet in this way:
Set<Car> car = new TreeSet<Car>(new Comparator<Car>() {
#Override
public int compare(Car car1, Car car2) {
int compPrice = - Float.compare(car1.getPrice(), car2.getPrice());
if (compPrice > 0 || compPrice < 0)
return compPrice;
else
return car1.getModel().compareTo(car2.getModel());
}});
car.add(new Car("carA",100f));
car.add(new Car("carB",101f));
car.add(new Car("carC",110f));
System.out.println(a.contains(new Car("carB",111f)));
But now there is a problem, it return FALSE... why?
It seems that when I invoke contains() using an arrayList the method equals() is invoked.
But it seems that when I invoke contains() using a TreeSet with a comparator, the comparator is used instead.
Why does that happen?
TreeSet forms a binary tree keeping elements according to natural (or not) orders, so in order to search quickly one specific element is the collection, TreeSet uses Comparable or Comparator instead of equals().
As TreeSet JavaDoc precises:
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.
We can find a similarity with the HashCode/Equals contract:
If equals() returns true, hashcode() has to return true too in order to be found during search.
Likewise with TreeSet:
If contains() (using Comparator or Comparable) returns true, equals() has to return true too in order to be consistent with equals().
THEREFORE: Fields used within TreeSet.equals() method have to be exactly the same (no more, no less) than within your Comparator implementation.
A TreeSet is implicitly sorted, and it uses a Comparator for this sorting. The equals() method can only tell you if two objects are the same or different, not how they should be ordered for sorting. Only a Comparator can do that.
More to the point, a TreeSet also uses comparisons for searching. This is sort of the whole point of tree-based map/set. When the contains() method is called, a binary search is performed and the target is either found or not found, based on how the comparator is defined. The comparator defines not only logical order but also logical identity. If you are relying on logical identity defined by an inconsistent equals() implementation, then confusion will probably ensue.
The reason for the different behaviour is, that you consider the price member in the compare method, but ignore it in equals.
new Car("carB",101f) // what you add to the list
new Car("carB",111f) // what you are looking for
Both instances are "equals" (sorry...) since their model members are equal (and the implementation stops after that test). They "compare" as different, though, because that implementation also checks the price member.
Related
I have a general method that needs to sort and search for generic objects,
Old Version:
public <T> int isIn(T[] list, T t) {
Arrays.sort(list, Comparator.comparingInt(Object::hashCode));
return Arrays.binarySearch(values,updatedObject.hashCode())
}
New Version:
public <T> int isIn(T[] list, T t) {
Arrays.sort(list, Comparator.comparingInt(Object::hashCode));
return Arrays.binarySearch(values,updatedObject.hashCode(),Comparator.comparingInt(Object::hashCode))
}
Assuming that hashcode() is implemented properly, I can't think of any case where this could fail or give any error.
What are the cases, if any, that this could give us an error !
NOTE: Code is edited, I added Comparator to the binary search
There are quite a few issues with your code.
hashCode is used to infer object inequality. Meaning that two objects may virtually have the same hashCode and yet an equals invocation may return false (see also here). In this case, the outcome of binarySearch may yield unexpected results (see also here).
binarySearch invocations without passing a Comparator assume that your objects are sorted based on their natural ordering, which implies they are Comparable (which we cannot know with your code). If your objects were not Comparable, you would get a ClassCastException when invoking binarySearch as you do. You would at the very least need to use the Comparator employed when sorting the array, and pass it to your binarySerach invocation (see here for overload API).
Note: OP added Comparator and edited question - only valid with previous version of the question.
As a general OO guideline, you may want to rethink what objects you are going to use your isIn method with, and maybe bind the generic type to a broad type that yields enough information in terms of properties to have a natural order, or at least to be sortable using a Comparator based on properties that it makes sense to sort with. TL;DR the hash code's purpose is not to sort objects.
Following up on point 3, you'd expect an isIn method to return a boolean type, not an integer type. If your goal is simply to infer whether your array contains a given value, override equals in the objects passed, wrap your array in a suitable Collection and invoke contains.
This should fail; because, when two objects contain the same hashcode the order will be unspecified. The contract on hash codes is that all objects returning equals(...) as true have the same hashcode, not all equal hashcodes have equals(...) return true.
Hashcodes are not unique, that's why we have the hashcode act as the first means of determining cheap equality, but always have to follow up with the equals(...) method afterwards.
Now, the equals(...) method doesn't provide comparision (ordering, so you'll have to back it up with a Comparator for the object anyway. Since you initially ordered on hashcode, your Comparator will have to provide orderings that don't violate the hascode-first approach.
// pseudocode
unless hashcodes are equal, return the value of Comparator.comparingInt()
when hashcodes are equal, pick some stable ordering.
Note that this would work well within it's bubble of your code; but, if you wanted to assure that someone didn't break this in the future, you might need to also add a Comparable interface to your object, and have it return the "hashcode, then sub-ordering" with the same logic as your Comparator.
Assuming that hashcode() is implemented properly
This is a big assumption as Java's library classes (e.g. String) themselves have collision in hashCode() values, e.g.
System.out.println("Aa".hashCode() + "," + "BB".hashCode());
This will print the same hash code and hence, comparator will consider both the objects equal.
Your code will produce an index of an object with the same hash code, but since objects are allowed to have the same hash code without being equal, you need to perform some additional work before returning the result.
Walk the list of items with the same hash code until you find equality, find a different hash code, or go off the end of the array:
public <T> int isIn(T[] list, T t) {
Comparator<T> cmp = Comparator.comparingInt(Object::hashCode);
Arrays.sort(list, cmp);
int pos = Arrays.binarySearch(list, t, cmp);
if (pos < 0) {
return -1;
}
// At this point pos is a valid index, but it may not be of the same object
// Continue with a linear search of the equal range of hash codes from here:
int hash = t.hashCode();
while (pos != list.length()) {
if (t.equals(list[pos]) {
return pos;
}
if (list[pos].hashCode() != hash) {
return -1;
}
pos++;
}
return -1;
}
Note: Although this is consistent with the way the hashCode/equals are supposed to be used in Java, the approach is less efficient than using a HashSet<T>, because it requires O(n*log n) sorting step.
I have a class that I'd like to put in a TreeSet, which implements Comparable to sort them by priority. Here's a small segment:
public abstract class PacketListener implements Comparable<PacketListener> {
public enum ListenerPriority {
LOWEST, LOW, NORMAL, HIGH, HIGHEST
}
private final ListenerPriority priority; // Initialized in constructor
// ... class body ...
#Override
public final int compareTo(PacketListener o) {
return priority.compareTo(o.priority);
}
}
The idea is obviously for the TreeSet to sort the objects by priority, allowing me to iterate through the listeners in order. However, I've discovered that, for some reason, I can't add a second PacketListener to the set object. After adding two different PacketListener objects, the size of the set remains at 1.
Should I not be using TreeSet?
The API docs for TreeSet contain this important information:
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. [...] 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.
In other words, a TreeSet can accommodate multiple instances of your PacketListener class, but only so long as each has a different priority than all the others, so that for every pair of elements A and B, exactly one of these holds: either A == B or A.compareTo(B) != 0.
If you must accommodate multiple PacketListener instances with the same priority in the same collection, then you need a different type of collection. HashSet is perfectly good with classes that use the equals() and hashCode() methods inherited from Object, provided that that is indeed the desired sense of instance equality. You could also consider a LinkedHashSet if you want some kind of guarantee about iteration order, or maybe a PriorityQueue if you want to order by priority but you are willing to use a different mechanism to avoid duplicates.
TreeSet treats two objects for which compareTo returns 0 to be equals. Which means you'll can never have two objects with the same priority in your tree set with your current implementation.
On way to fix your problem is to make your compareTo method consider all values that you care about (i.e. only objects that are actually equal return 0).
A Set is a collection of unique elements. As you are using the priority to compare PacketListener, I assume that there are at most five instances in you TreeSet, one for each priority.
If the structure allows you, you can find a secondary key to compare PacketListener, in case they have the same priority. If you can't, then TreeSet is the wrong way to go.
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.)
I found this question from a book but it was hard to figure out the way behind the answer.
public class Drink implements Comparable{
public String name ;
public int compareTo(Object o){
return 0;
}
}
and:
Drink one=new Drink();
Drink two=new Drink();
one.name="Coffee";
two.name="Tea";
TreeSet set=new TreeSet();
set.add(one);
set.add(two);
If a programmer iterates over the TreeSet and prints the name of each Drink object, what is the result?
Could someone please help me to get the answer by describing the way behind it.
Only "Coffee" (one) is added to the set, since the TreeSet returns equality according to the compareTo(), and since the element is already in the set, noting will happen when you try to add two.
From the java docs:
TreeSet:
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.
add():
...
If this set already contains the element, the call leaves the set
unchanged and returns false.
Also note, that this is unadvised behavior that do not follow the Set interface contract, since you did not override equals().
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.