Gee*_*eek 17 java comparison equals comparable comparator
从TreeMap的JavaDoc:
请注意,如果此有序映射要正确实现Map接口,则由有序映射维护的排序(无论是否提供显式比较器)必须与equals一致.(请参阅Comparable或Comparator以获得与equals一致的精确定义.)这是因为Map接口是根据equals操作定义的,但是map使用compareTo(或compare)方法执行所有键比较,因此有两个键从排序地图的角度来看,通过这种方法被视为相等的是相等的.即使排序与equals不一致,也可以很好地定义有序映射的行为.它只是不遵守Map接口的一般合同.
有人可以给出一个具体的例子来说明如果排序与equals不一致可能会出现的问题吗?举例来说,用户定义的类具有自然顺序,即它实现了Comparable.JDK中的所有内部类都保持这个不变量吗?
Stu*_*rks 23
这是一个简单但实际的例子,说明如果比较方法与equals不一致会发生什么.在JDK中,BigDecimal实现Comparable但其比较方法与equals不一致.例如:
> BigDecimal z = new BigDecimal("0.0")
> BigDecimal zz = new BigDecimal("0.00")
> z.compareTo(zz)
0
> z.equals(zz)
false
Run Code Online (Sandbox Code Playgroud)
这是因为比较方法BigDecimal只考虑数值,但equals也考虑精度.由于0.0与0.00具有不同的精度,他们即使它们具有相同的数值不等.
这是一个TreeSet违反一般合同意味着什么的例子Set.(这是用同样的情况TreeMap,并Map但它是一个有点容易使用套来证明.)让我们比较的结果contains,以获得元素进行设置和调用的结果equals:
> TreeSet<BigDecimal> ts = new TreeSet<>()
> ts.add(z)
> ts.contains(z)
true
> z.equals(ts.iterator().next())
true
> ts.contains(zz)
true
> zz.equals(ts.iterator().next())
false
Run Code Online (Sandbox Code Playgroud)
这里令人惊讶的是TreeSet它说它包含了对象zz,但是它与实际包含在集合中的元素不相等.原因是TreeSet使用其比较方法(BigDecimal.compareTo)来确定集合成员资格,而不是equals.
现在让我们比较TreeSet到HashSet:
> HashSet<BigDecimal> hs = new HashSet<>(ts)
> hs.equals(ts)
true
> ts.contains(zz)
true
> hs.contains(zz)
false
Run Code Online (Sandbox Code Playgroud)
这很奇怪.我们有两个相同的集合,但是一个集合表示它包含一个对象,而另一个集合表示它不包含相同的对象.同样,这反映了一个事实TreeSet是,而使用比较方法HashSet是使用equals.
现在让我们将另一个对象添加到a中HashSet,看看会发生什么:
> HashSet<BigDecimal> hs2 = new HashSet<>()
> hs2.add(zz)
> ts.equals(hs2)
true
> hs2.equals(ts)
false
Run Code Online (Sandbox Code Playgroud)
现在这很奇怪.一组说它等于另一组,但另一组说它不等于第一组!要理解这一点,您需要了解如何确定集合的相等性.如果a)它们具有相同的大小,则认为两组相等,并且b)另一组中的每个元素也包含在该组中.也就是说,如果你有
set1.equals(set2)
Run Code Online (Sandbox Code Playgroud)
然后,相等算法查看大小,然后迭代set2,并为每个元素检查该元素是否包含在set1中.这就是不对称性的来源.当我们这样做时
ts.equals(hs2)
Run Code Online (Sandbox Code Playgroud)
两个集合的大小都是1,因此我们继续进行迭代步骤.我们迭代hs2并使用然后调用TreeSet.contains方法 - 它使用比较方法.就所TreeSet涉及的而言,它等于HashSeths2.
现在我们做的时候
hs2.equals(ts)
Run Code Online (Sandbox Code Playgroud)
比较是另一回事.我们遍历TreeSet并获取其元素,并询问hs2它是否是contains该元素.由于HashSet.contains使用等于,它返回false,整体结果为false.
Tom*_*icz 19
假设我们有这个简单的Student类实现Comparable<Student>但不覆盖equals()/ hashCode().当然equals()不一致compareTo()- 两个不同的学生age不一样:
class Student implements Comparable<Student> {
private final int age;
Student(int age) {
this.age = age;
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
@Override
public String toString() {
return "Student(" + age + ")";
}
}
Run Code Online (Sandbox Code Playgroud)
我们可以安全地使用它TreeMap<Student, String>:
Map<Student, String> students = new TreeMap<Student, String>();
students.put(new Student(25), "twenty five");
students.put(new Student(22), "twenty two");
students.put(new Student(26), "twenty six");
for (Map.Entry<Student, String> entry : students.entrySet()) {
System.out.println(entry);
}
System.out.println(students.get(new Student(22)));
Run Code Online (Sandbox Code Playgroud)
结果很容易预测:学生根据他们的年龄(尽管以不同的顺序插入)很好地分类,并且使用new Student(22)关键作品获取学生并返回"twenty two".这意味着我们可以安全地使用Student课程TreeMap.
但是更改students到HashMap,事情变坏:
Map<Student, String> students = new HashMap<Student, String>();
Run Code Online (Sandbox Code Playgroud)
显然,由于散列,项目的枚举返回"随机"顺序 - 这很好,它不违反任何Map合同.但最后一句话完全被打破了.因为HashMap使用equals()/ hashCode()比较实例,new Student(22)按键获取值失败并返回null!
这就是JavaDoc试图解释的内容:这些类可以使用,TreeMap但可能无法与其他Map实现一起使用.请注意,Map操作是以equals()/ 为单位记录和定义的hashCode(),例如containsKey():
当且仅当此映射包含键k的映射时,[...]才返回true
(key==null ? k==null : key.equals(k))
因此,我不相信有任何标准的JDK类可以实现Comparable但无法实现equals()/ hashCode()配对.
ass*_*ias 13
Comparable接口的契约允许不一致的行为:
强烈建议(尽管不要求)自然排序与equals一致.
所以从理论上讲,JDK中的一个类可能compareTo与之不一致equals.一个很好的例子是BigDecimal.
下面是一个与equals不一致的比较器的设计示例(它基本上表示所有字符串都相等).
输出:
大小:1
内容:{a = b}
public static void main(String[] args) {
Map<String, String> brokenMap = new TreeMap<String, String> (new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return 0;
}
});
brokenMap.put("a", "a");
brokenMap.put("b", "b");
System.out.println("size: " + brokenMap.size());
System.out.println("content: " + brokenMap);
}
Run Code Online (Sandbox Code Playgroud)
小智 7
这是另一个与equals 和AND排序的一致性实现重要的例子.
假设我们有一个MyObject有两个字段的对象:id和quantity.
id顾名思义,它是对象的自然键,quantity只是一个属性.
public class MyObject {
int id;
int quantity;
...
}
Run Code Online (Sandbox Code Playgroud)
让我们假设我们想要使用MyObject按quantity降序排序的集合.我们可以写的第一个比较器是:
Comparator<MyObject> naiveComp = new Comparator<MyObject>() {
@Override
public int compare(MyObject o1, MyObject o2) {
return o2.quantity - o1.quantity;
}
};
Run Code Online (Sandbox Code Playgroud)
MyObject在TreeMap/TreeSet中使用配备此比较器的实例会失败,因为它的比较器与equals不一致(请参阅下面的完整代码).让它与equals保持一致:
Comparator<MyObject> slightlyBetterComp = new Comparator<MyObject>() {
@Override
public int compare(MyObject o1, MyObject o2) {
if (o1.equals(o2)) {
return 0;
}
if (o1.quantity == o2.quantity) {
return -1; // never 0
}
return o2.quantity - o1.quantity; // never 0
}
};
Run Code Online (Sandbox Code Playgroud)
但是,这再次失败以适应TreeSet/TreeMap!(参见下面的完整代码)这是因为排序关系不是完全的,即不能将任何两个对象严格地置于排序关系中.在该比较器中,当quantity字段相等时,得到的排序是不确定的.
一个更好的比较器是:
Comparator<MyObject> betterComp = new Comparator<MyObject>() {
@Override
public int compare(MyObject o1, MyObject o2) {
if (o1.equals(o2)) {
return 0;
}
if (o1.quantity == o2.quantity) {
return o1.id - o2.id; // never 0
}
return o2.quantity - o1.quantity; // never 0
}
};
Run Code Online (Sandbox Code Playgroud)
该比较器确保:
equal(初始检查相等)id当quantity相等时,所有项目都作为判别排序字段使用完整测试代码:
package treemap;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class MyObject {
int id;
int quantity;
public MyObject(int id, int quantity) {
this.id = id;
this.quantity = quantity;
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + this.id;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final MyObject other = (MyObject) obj;
if (this.id != other.id) {
return false;
}
return true;
}
@Override
public String toString() {
return "{" + id + ", " + quantity + "}";
}
public static void main(String[] args) {
String format = "%30.30s: %s\n";
Map<MyObject, Object> map = new HashMap();
map.put(new MyObject(1, 100), 0);
map.put(new MyObject(2, 100), 0);
map.put(new MyObject(3, 200), 0);
map.put(new MyObject(4, 100), 0);
map.put(new MyObject(5, 500), 0);
System.out.printf(format, "Random Order", map.keySet());
// Naive non-consisten-with-equal and non-total comparator
Comparator<MyObject> naiveComp = new Comparator<MyObject>() {
@Override
public int compare(MyObject o1, MyObject o2) {
return o2.quantity - o1.quantity;
}
};
Map<MyObject, Object> badMap = new TreeMap(naiveComp);
badMap.putAll(map);
System.out.printf(format, "Non Consistent and Non Total", badMap.keySet());
// Better consisten-with-equal but non-total comparator
Comparator<MyObject> slightlyBetterComp = new Comparator<MyObject>() {
@Override
public int compare(MyObject o1, MyObject o2) {
if (o1.equals(o2)) {
return 0;
}
if (o1.quantity == o2.quantity) {
return -1; // never 0
}
return o2.quantity - o1.quantity; // never 0
}
};
Map<MyObject, Object> slightlyBetterMap = new TreeMap(naiveComp);
slightlyBetterMap.putAll(map);
System.out.printf(format, "Non Consistent but Total", slightlyBetterMap.keySet());
// Consistent with equal AND total comparator
Comparator<MyObject> betterComp = new Comparator<MyObject>() {
@Override
public int compare(MyObject o1, MyObject o2) {
if (o1.equals(o2)) {
return 0;
}
if (o1.quantity == o2.quantity) {
return o1.id - o2.id; // never 0
}
return o2.quantity - o1.quantity; // never 0
}
};
Map<MyObject, Object> betterMap = new TreeMap(betterComp);
betterMap.putAll(map);
System.out.printf(format, "Consistent and Total", betterMap.keySet());
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
Random Order: [{5, 500}, {4, 100}, {3, 200}, {2, 100}, {1, 100}]
Non Consistent and Non Total: [{5, 500}, {3, 200}, {4, 100}]
Consistent but Not Total: [{5, 500}, {3, 200}, {4, 100}]
Consistent and Total: [{5, 500}, {3, 200}, {1, 100}, {2, 100}, {4, 100}]
Run Code Online (Sandbox Code Playgroud)
结论:
虽然我认为从概念上将身份与排序隔离是非常合理的.例如,在关系数据库术语中:
select * from MyObjects order by quantity
Run Code Online (Sandbox Code Playgroud)
工作得很好.我们不关心这里的对象身份,也不想要总排序
但是,由于基于树的集合实现中的约束,必须确保他们编写的任何比较器:
| 归档时间: |
|
| 查看次数: |
10006 次 |
| 最近记录: |