Reputation: 53
The following code should print 3 persons, but it actually print 4 persons, why? Person("a", 1) and Person("a", 4) should be treated as the same, but they are not.
import java.util.TreeSet;
public class Person implements Comparable<Person>{
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String name;
public int age;
@Override
public int compareTo(Person o) {
if(this.name.equals(o.name)){
return 0;
}else if(this.age <= o.age){
return -1;
}else {
return 1;
}
}
@Override public boolean equals(Object other) {
return name.equals(((Person)other).name);
}
@Override public int hashCode() {
return age * 31;
}
public static void main(String[] args) {
TreeSet<Person> persons = new TreeSet<Person>();
persons.add(new Person("a", 1));
persons.add(new Person("b", 3));
persons.add(new Person("c", 2));
persons.add(new Person("a", 4));//This should be ignored.
/*Here should print the first 3 persons, but it prints 4 persons*/
for(Person h: persons){
System.out.println(h.name + ":" +h.age);
}
}
}
Result:
a:1
c:2
b:3
a:4
Upvotes: 4
Views: 451
Reputation: 30335
The problem is the compareTo
method, which performs comparisons based on age, but determines equality based on the Person's name, making it inconsistent with itself.
After inserting the first 3 Persons
, the tree looks something like this (forgive the poor ASCII art):
(c,2)
|
/\
(a,1) (b,3)
Then (a,4) is inserted, it's compared to (c,2) based on the age. The age (4) is bigger than (2), so we go to the right. The next comparison is with (b,3). (a,4) is again bigger, so it's added as a new node to the tree (since there's nothing else to compare to). If instead of adding (a,4) you'd add (a,0), then there would be a comparison with (a,1), and the result would be:
a:1
c:2
b:3
Upvotes: 4
Reputation: 692181
Your compareTo method doesn't respect the contract of Comparable.compareTo()
.
It should be symmetric: if a < b
, then b > a
. That is not the case since if a
and b
have the same name and the same age, a.compareTo(b)
is -1, and b.compareTo(a)
is -1 as well. So a
is bigger than b
and smaller than b
at the same time.
It should be transitive: if a < b
and b < c
, then a < c
. This is not the case:
(a, 3) < (b, 7)
, (b, 7) < (a, 10)
(a, 3) = (a, 10)
Given that the contract is not respected, you can't expect anything from TreeSet, which assumes that the contract is respected to work.
Upvotes: 3
Reputation: 280179
A TreeSet
is implemented with a TreeMap
which is a red black tree. When you add the first element, it becomes root
(A, 1)
When you add the second, it has a differdnt name and a bigger age, so it becomes the root's right child.
(A, 1)
\
(B, 2)
When you add the third, it's again bigger than those. The tree reorganizes itself for balance.
(B, 2)
/ \
(A, 1) (C, 3)
You then add your final element. 4 is bigger than 2, so search goes down the right branch and doesn't find an element with name "a", so it adds a new entry.
(B, 2)
/ \
(A, 1) (C, 3)
\
(A, 4)
Upvotes: 3
Reputation: 3201
Your implementation of compareTo
doesn't match your implementation of equals
. TreeSet
operates on compareTo
.
As a side note: The implementation of hashCode()
is also wrong, as it's not in line with equals
either.
Upvotes: 1