Java 比较器与违反set合同
Comparator的JavaDoc声明 强烈建议(尽管不是必需的)自然顺序与equals一致 他们还举了一个当Java 比较器与违反set合同,java,equals,comparator,contract,Java,Equals,Comparator,Contract,Comparator的JavaDoc声明 强烈建议(尽管不是必需的)自然顺序与equals一致 他们还举了一个当(a.equals(b)&&c.compare(a,b)!=0)时的“奇怪”行为的例子 现在,有人能给我举一个例子说明在case(!a.equals(b)和&c.compare(a,b)==0)中的“奇怪”行为吗?第二种情况应该比第一种情况更频繁,因为在实现比较器时,很容易忘记对比较类型实现等于。在不知道的情况下很难给出一个示例,例如,TreeSet (为什么这个问题与我有关,这是一个
(a.equals(b)&&c.compare(a,b)!=0)
时的“奇怪”行为的例子
现在,有人能给我举一个例子说明在case(!a.equals(b)和&c.compare(a,b)==0)
中的“奇怪”行为吗?第二种情况应该比第一种情况更频繁,因为在实现比较器
时,很容易忘记对比较类型实现等于
。在不知道的情况下很难给出一个示例,例如,TreeSet
(为什么这个问题与我有关,这是一个较长的故事。这不是一个家庭作业)集合的JDK实现依赖于这样的行为关系。另一个很好的例子是HashSet,它依赖于
equals()
和hashCode()
同意
“奇怪”的意思是“未定义”——没有定义这些类在使用不遵循规则的类时的行为。它们可以完美地工作,也可以不完美。但是,如果您的元素类不遵循javadoc中描述的行为,您就不能依靠它们正常工作。假设以下API:
final class Foo {
int bar;
Foo(int bar) { this.bar = bar; }
public int hashCode() {
return bar;
}
public boolean equals(Object o) {
return o instanceof Foo && ((Foo)o).bar == bar;
}
}
static Set<Foo> getSetOpaquely() {???}
假设这是一个带有比较器的树集,当它打印出true
时我感到惊讶
(lhs, rhs) -> lhs == rhs ? 0 : 1
(lhs, rhs) -> Integer.compare(lhs.bar % 10, rhs.bar % 10)
现在,有人能给我举一个例子说明在case(!a.equals(b)和&c.compare(a,b)==0)
中的“奇怪”行为吗
我想是的
Set<Foo> mySet = getSetOpaquely();
mySet.add(new Foo(1));
System.out.println(mySet.add(new Foo(1));
Set<Foo> mySet = getSetOpaquely();
mySet.add(new Foo(102));
System.out.println(mySet.add(new Foo(12));
现在,定义与
equals
不一致的顺序并不存在固有的问题。关键是树集的行为可能与集合的文档中指定的行为不同
这是:
[…]根据equals操作定义了Set
接口,但是TreeSet
实例使用其compareTo
(或compare
)方法执行所有元素比较,因此从集合的角度来看,此方法认为相等的两个元素是相等的集的行为是定义良好的,即使其顺序与equals不一致;它只是没有遵守集合
接口的总合同。
只要比较器不是黑客,并且您知道它是一个具有特定顺序的树集,您就不会感到惊讶。(如果它像
(lhs,rhs)->1,你可能会感到惊讶。)这里是一个简单的演示。我们有一个名为Strange
的类,它使用不区分大小写的字符串比较来实现equals
和hashCode
,但实现了区分大小写的compareTo
class Strange implements Comparable<Strange> {
final String s;
public Strange(String s) {
this.s = s;
}
@Override
public boolean equals(Object o) {
// Kind of equals - case insensitive.
return (o instanceof Strange) && ((Strange) o).s.equalsIgnoreCase(s);
}
@Override
public int hashCode() {
// Consistent with equals.
return s.toUpperCase().hashCode();
}
@Override
public int compareTo(Strange o) {
// Exact ordering including case - inconsistent with equals.
return s.compareTo(o.s);
}
@Override
public String toString() {
return s;
}
}
public void test() {
Set<Strange> set1 = new HashSet<>();
Set<Strange> set2 = new TreeSet<>();
for (String s : new String[]{"Hello", "hello", "Everyone", "everyone"}) {
Strange strange = new Strange(s);
set1.add(strange);
set2.add(strange);
}
System.out.println("Set1: " + set1);
System.out.println("Set2: " + set2);
}
请参见如何将字符串放入树集
更改结果?这是因为TreeSet
使用compareTo
,而HashSet
使用equals
和hashCode
。这可能会以许多不同的方式(最重要的是出乎意料的)破坏事物,因为您不必知道幕后正在使用什么类型的集合
这表明(a.equals(b)&&a.compareTo(b)!=0)
给出了奇怪的结果。很容易看出相反的问题(!a.equals(b)&&a.compareTo(b)==0)
也显示出奇怪的结果。如果你阅读集合的文档,它会说如果你有两个相等的对象,那么集合中只存储一个(或两个都不存储)。如果树集使用的比较器与等号不一致,则这是不正确的。请参见我的回答:谢谢,斯图尔特·马克斯。你能把你的评论作为常规的回答,这样我就可以接受了吗?当然,这种行为是没有定义的。但通常情况下,存在一种确定性行为,这取决于具体的实现(有时还取决于其他因素,如虚拟机版本、操作系统等),我希望在给定的合同违约情况下有一个具体的非设置行为示例。
Set1: [Hello, Everyone]
Set2: [Everyone, Hello, everyone, hello]