我用
Triple
和equals
方法定义了一个hashcode
类。根据文档,Java HashSet 的HashSet.contains()
方法必须返回与Objects.equals()
相同的值。但是 contains()
Java HashSet 方法返回 true,当它假设为 false.
class Triple{
private List<Integer> triple;
public Triple(int one, int two, int three){
this.triple = new ArrayList<>();
this.triple.add(one);
this.triple.add(two);
this.triple.add(three);
}
public int hashCode(){
return triple.get(0).hashCode() + triple.get(1).hashCode() + triple.get(2).hashCode();
}
public boolean equals(Object t){
Triple triple = (Triple)t;
return triple.triple.contains(this.triple.get(0))
&& triple.triple.contains(this.triple.get(1))
&& triple.triple.contains(this.triple.get(2));
}
}
public class Main {
public static void main(String[] args) {
HashSet<Triple> answersSet = new HashSet<>();
Triple triple1 = new Triple(-4,0,4);
Triple triple2 = new Triple(0,0,0);
answersSet.add(triple1);
boolean b = answersSet.contains(triple2); //returns true
b = Objects.equals(triple1,triple2); //but it returns false,
}
}
boolean b = answersSet.contains(triple2); //返回真
确实如此,因为您的
equals
方法以及您的 hashCode()
方法在此处产生相同的值。乍一看,它不像。但是,实际上,这就是您的代码所做的。让我们通过它:
返回 triple.get(0).hashCode() + triple.get(1).hashCode() + triple.get(2).hashCode();
Integer
值的 hashCode 就是..本身。因此,对于 -4,0,4
,您最终会得到一个 0 的 hashCode。毕竟,-4 + 0 + +4 是 0.
0, 0, 0
的哈希码也是0.
这称为散列冲突。这并不意味着您对
hashCode()
的实施肯定是坏的;它发生了。通过简单的鸽巢原理:有2^32种不同的可能的哈希码,并且有2^96种不同的Triple
实例;因此,必须至少有 2^64 个不同的Triple
实例仍然具有相同的 hashCode。
这很好——规范中没有任何内容要求不同的对象具有不同的哈希码。规范只规定了两件事:
你的散列分布大部分都很好。通过考虑数字的位置,您可以做得更好。像这样的东西:
@Override public int hashCode() {
return triple.get(0) + triple.get(1) * 31 + triple.get(2) * 97;
31 和 97 是质数。那是故意的。
您的 equals 方法已损坏。它不关心秩序!它说相等意味着提供的
Triple
对象包含this
三元组的每个值。
鉴于
this
三元组是 0/0/0,您检查传入的 triple
是否包含 0(确实如此)。然后它检查它是否包含 0(它..仍然包含),然后第三次检查它,它..它仍然包含..
这真的,真的坏了。它与 hashCode 冲突(0/0/0 和 5/0/4 是相等的,但没有相同的 hashCode,这是违规的),并且是一个奇怪的相等定义。它也是不可逆的,并且 equals 方法必须是:如果
a.equals(b)
为真,则 b.equals(a)
也必须为真,但是对于 0/0/0 和 -4/0/4 这也不是真的.
另外,如果您向它传递一个非三元组,您的 equals 方法将抛出一个
ClassCastException
,这也被破坏了。 equals 的文档描述了你必须做什么。你违反了它的 3 条规则。
让我们解决所有问题:
修复是检查每个位置:
@Override public boolean equals(Object other) {
if (other == null || other.getClass() != Triple.class) return false;
Triple t = (Triple) other;
return t.triple.get(0).equals(this.triple.get(0)) &&
t.triple.get(1).equals(this.triple.get(1)) &&
t.triple.get(2).equals(this.triple.get(2));
}
使用记录,或者,lombok 的
@Value
为您处理这一切:
public record Triple(int a, b, c) {}
或
@Value
public class Triple {
List<Integer> triple;
public Triple(int a, b, c) {
this.triple = List.of(a, b, c);
}
}
更一般地说,感觉这个类应该有 3 个
int
类型的字段,而不是一个 List<Integer>
类型的字段。
你的
equals
坏了。您只测试该值是否包含在List
中,而不是三个值是否相等。我想你想要
@Override
public boolean equals(Object o) {
Triple t = (Triple) o;
return triple.get(0).equals(t.triple.get(0))
&& triple.get(1).equals(t.triple.get(1))
&& triple.get(2).equals(t.triple.get(2));
}
哪个(显然)返回
false
与您的示例数据。此外,您可以简化构造函数
public Triple(int one, int two, int three) {
this.triple = List.of(one, two, three);
}
如评论中所讨论,并在其他答案中指出;您最初的问题是由基于简单加法的哈希码引起的,其中所有三个值的总和相同。你的 equals 被打破了,因为
a
可以等于 b
而 b
等于 a
(因为 a
可能包含一个存在于 b
中的值,而 b
可能包含三个值只有一个他们出现在a
)。最好将这三个值存储为 int
字段。您还应该使 Triple
不可变,因为更改任何字段都会改变实例的哈希码。您也可以覆盖 toString
并在您使用它时将其设为 Comparable
。所以最终版本可能看起来像,
class Triple implements Comparable<Triple> {
private final int a, b, c;
public Triple(int one, int two, int three) {
this.a = one;
this.b = two;
this.c = three;
}
@Override
public int hashCode() {
return java.util.Objects.hash(this.a, this.b, this.c);
}
@Override
public boolean equals(Object o) {
if (o instanceof Triple) {
Triple that = (Triple) o;
return this.a == that.a
&& this.b == that.b
&& this.c == that.c;
}
return false;
}
@Override
public String toString() {
return String.format("(%d, %d, %d)", a, b, c);
}
@Override
public int compareTo(Triple that) {
int comp = Integer.compare(this.a, that.a);
if (comp != 0) {
return comp;
}
comp = Integer.compare(this.b, that.b);
if (comp != 0) {
return comp;
}
return Integer.compare(this.c, that.c);
}
}
要使两个列表进行相等比较,每个列表必须具有
same size
并且 list1.get(i).equals(list2.get(i))
必须是 true
对于 0 <= i < List.size()
这很容易通过以下方式实现:
instanceof
(Java 16+)如果为真,它将obj
转换并分配给t
。public boolean equals(Object obj) {
if (obj instanceof Triple t) {
return t.triple.equals(this.triple);
}
return false;
}
此外,您可以按如下方式创建
hashCode
。
public int hashCode() {
return Objects.hash(triple.get(0), triple.get(1), triple.get(2));
}