为什么如果compareTo()返回0就暗示对象相等?
让我们有一个Person
类。人有名字和身高。
Equals 和 hashCode() 仅考虑名称。人是可比较的(或者我们为其实现比较器,无论是哪一个)。人是按身高进行比较的。
预期两个不同的人可以具有相同高度的情况似乎是合理的,但例如 TreeSet 的行为类似于compareTo()==0 意味着等于,而不仅仅是相同的大小。
为了避免这种情况,如果大小相同,比较可以其次查看其他东西,但它不能用于检测相同大小的不同对象。
示例:
import java.util.Comparator;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
public class Person implements Comparable<Person> {
private final String name;
private int height;
public Person(String name,
int height) {
this.name = name;
this.height = height;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getName() {
return name;
}
@Override
public int compareTo(Person o) {
return Integer.compare(height, o.height);
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Person other = (Person) obj;
if (!Objects.equals(this.name, other.name)) {
return false;
}
return true;
}
public int hashCode() {
int hash = 5;
hash = 13 * hash + Objects.hashCode(this.name);
return hash;
}
public String toString() {
return "Person{" + name + ", height = " + height + '}';
}
public static class PComparator1 implements Comparator<Person> {
@Override
public int compare(Person o1,
Person o2) {
return o1.compareTo(o2);
}
}
public static class PComparator2 implements Comparator<Person> {
@Override
public int compare(Person o1,
Person o2) {
int r = Integer.compare(o1.height, o2.height);
return r == 0 ? o1.name.compareTo(o2.name) : r;
}
}
public static void test(Set<Person> ps) {
ps.add(new Person("Ann", 150));
ps.add(new Person("Jane", 150));
ps.add(new Person("John", 180));
System.out.println(ps.getClass().getName());
for (Person p : ps) {
System.out.println(" " + p);
}
}
public static void main(String[] args) {
test(new HashSet<Person>());
test(new TreeSet<Person>());
test(new TreeSet<>(new PComparator1()));
test(new TreeSet<>(new PComparator2()));
}
}
结果:
java.util.HashSet
Person{Ann, height = 150}
Person{John, height = 180}
Person{Jane, height = 150}
java.util.TreeSet
Person{Ann, height = 150}
Person{John, height = 180}
java.util.TreeSet
Person{Ann, height = 150}
Person{John, height = 180}
java.util.TreeSet
Person{Ann, height = 150}
Person{Jane, height = 150}
Person{John, height = 180}
你知道为什么会这样吗?
Let's have a class Person
. Person has a name and height.
Equals and hashCode() takes into account only name. Person is comparable (or we implement comparator for it, does not matter which one). Persons are compared by height.
It seems reasonable to expect a situation where two different persons can have same height, but e.g. TreeSet behaves like compareTo()==0 means equals, not merely same size.
To avoid this, comparison can secondarily look at something else if size is the same, but then it cannot be used to detect same sized different objects.
Example:
import java.util.Comparator;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
public class Person implements Comparable<Person> {
private final String name;
private int height;
public Person(String name,
int height) {
this.name = name;
this.height = height;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getName() {
return name;
}
@Override
public int compareTo(Person o) {
return Integer.compare(height, o.height);
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Person other = (Person) obj;
if (!Objects.equals(this.name, other.name)) {
return false;
}
return true;
}
public int hashCode() {
int hash = 5;
hash = 13 * hash + Objects.hashCode(this.name);
return hash;
}
public String toString() {
return "Person{" + name + ", height = " + height + '}';
}
public static class PComparator1 implements Comparator<Person> {
@Override
public int compare(Person o1,
Person o2) {
return o1.compareTo(o2);
}
}
public static class PComparator2 implements Comparator<Person> {
@Override
public int compare(Person o1,
Person o2) {
int r = Integer.compare(o1.height, o2.height);
return r == 0 ? o1.name.compareTo(o2.name) : r;
}
}
public static void test(Set<Person> ps) {
ps.add(new Person("Ann", 150));
ps.add(new Person("Jane", 150));
ps.add(new Person("John", 180));
System.out.println(ps.getClass().getName());
for (Person p : ps) {
System.out.println(" " + p);
}
}
public static void main(String[] args) {
test(new HashSet<Person>());
test(new TreeSet<Person>());
test(new TreeSet<>(new PComparator1()));
test(new TreeSet<>(new PComparator2()));
}
}
result:
java.util.HashSet
Person{Ann, height = 150}
Person{John, height = 180}
Person{Jane, height = 150}
java.util.TreeSet
Person{Ann, height = 150}
Person{John, height = 180}
java.util.TreeSet
Person{Ann, height = 150}
Person{John, height = 180}
java.util.TreeSet
Person{Ann, height = 150}
Person{Jane, height = 150}
Person{John, height = 180}
Do you have idea why it is so?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
从
java.util.SortedSet
javadoc 中摘录:因此,换句话说,
SortedSet
打破(或“扩展”)了Object.equals()
和Comparable.compareTo
的一般约定。请参阅compareTo
的合约:Extract from the
java.util.SortedSet
javadoc:Hence, in other words,
SortedSet
breaks (or "extends") the general contracts forObject.equals()
andComparable.compareTo
. See the contract forcompareTo
:如果对相同对象调用
equals
将返回true
,则建议compareTo
仅返回0
:(来自 JDK 1.6 Javadocs)
It is recommended that
compareTo
only returns0
, if a call toequals
on the same objects would returntrue
:(From the JDK 1.6 Javadocs)
TreeSet
不使用哈希码和相等性进行操作 - 它仅根据您提供的比较器进行操作。请注意,Javadoc 指出:在您的情况下,您的比较*与
equals
不一致,因此您的集合不遵守Set
的一般契约。为什么不在比较中添加更多方面,以便只有相等的元素才与 0 的结果进行比较?
TreeSet
doesn't operate using hash codes and equality - it only operates on the basis of the comparator you give it. Note that the Javadoc states:In your case, your comparison *isn't consistent with
equals
, so your set doesn't obey the general contract ofSet
.Why not just add more aspects to the comparison so that only equal elements compare with a result of 0?
当高度相等时,您可以通过使用 name 进行另一次比较来修复此问题,
因为名称是唯一的,如果
this.equals(o)
则仅返回 0you can fix it by using name for another comparison when the heights are equal
since names are unique this will only return 0 if
this.equals(o)
强烈建议,但不严格要求
(x.compareTo(y)==0) == (x.equals(y))代码> [1]
所以你比较一下就好了与您记录的
equals
上使用的标准不同。但是,如果您按照相同的标准进行比较,并且如果需要的话,提供一个适用于新标准的自定义比较器(在 Person 的情况下为身高)会更好
It is strongly recommended, but not strictly required that
(x.compareTo(y)==0) == (x.equals(y))
[1]So it's fine that you compare with a different criteria than the used on
equals
granted that you document it.However it would be better if your compare by the same criteria and if needed provide a custom comparator which works on the new criteria ( height in the case of Person )
当您为 Person 提供一个比较 Person 的高度属性的实例时,这实际上意味着如果两个 Person 实例具有相同的高度,则它们是相同的。
您必须创建一个特定于 Person 类的比较器。
When you give Person a Comparator that compares instances on the height attribute of the Person, it really means that two Person instances are the same if they have the same height.
You will have to make a Comparator that is specific for class Person.