[英]Why does super.hashCode give different results on objects from the same Class?
I have a class DebugTo
where if I have two equal instances el1
, el2
a HashSet of el1
will not regard el2
as contained.我有一个 class
DebugTo
,如果我有两个相等的实例el1
和el2
,则el1
的 HashSet 不会将el2
视为已包含。
import java.util.Objects;
public class DebugTo {
public String foo;
public DebugTo(String foo) {
this.foo = foo;
}
@Override
public int hashCode() {
System.out.println(super.hashCode());
return Objects.hash(super.hashCode(), foo);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DebugTo that = (DebugTo) o;
return Objects.equals(foo, that.foo);
}
}
var el1 = new DebugTo("a");
var el2 = new DebugTo("a");
System.out.println("Objects.equals(el1, el2): " + Objects.equals(el1, el2));
System.out.println("Objects.equals(el2, el1): " + Objects.equals(el2, el1));
System.out.println("el1.hashCode(): " + el1.hashCode());
System.out.println("el2.hashCode(): " + el2.hashCode());
Objects.equals(el1, el2): true
Objects.equals(el2, el1): true
1205483858
el1.hashCode(): -1284705008
1373949107
el2.hashCode(): -357249585
From my analysis I have gathered that:根据我的分析,我收集到:
HashSet::contains
calls hashCode
not equals
(relying on the Objects.equals(a, b)
=> a.hashSet() == b.hashSet()
) HashSet::contains
调用hashCode
不equals
(依赖于Objects.equals(a, b)
=> a.hashSet() == b.hashSet()
)super.hashCode()
gives a different value both times. super.hashCode()
给出不同的值。 Why does super.hashCode()
give different results for el1
and el2
?为什么
super.hashCode()
对el1
和el2
给出不同的结果? since they are of the same class, they have the same super class and so I expect super.hashCode()
to give the same result for both.因为它们是相同的 class,所以它们具有相同的超级 class,所以我希望
super.hashCode()
对两者给出相同的结果。
The hashCode method was probably autogenerated by eclipse. If not answered above, why is super.hashCode used wrong here? hashCode方法应该是eclipse自动生成的。如果上面没有回答,为什么这里super.hashCode用错了?
Because the default implementations of the equals
and hashCode
methods (which go hand in hand - you always override both or neither) treat any 2 different instances as not equal to each other.因为
equals
和hashCode
方法的默认实现(其中 go 手牵手 - 你总是覆盖两者或两者都不覆盖)将任何 2 个不同的实例视为彼此不相等。 If you want different behaviour, you override equals and hashCode, and do not invoke super.equals
/ super.hashCode
, or there'd be no point.如果您想要不同的行为,您可以覆盖 equals 和 hashCode,并且不要调用
super.equals
/ super.hashCode
,否则就没有意义了。
HashSets work as follows: They use .hashCode()
to know which 'bucket' to put the object into, and if 2 objects end up in the same bucket, equals
is used only on those very few objects to double check. HashSets 的工作方式如下:它们使用
.hashCode()
来知道将 object 放入哪个“桶”,如果 2 个对象最终出现在同一个桶中,则仅对极少数对象使用equals
进行双重检查。
In other words, these are the rules:换句话说,这些是规则:
a.equals(b)
, then b.equals(a)
must be true.a.equals(b)
,则b.equals(a)
必须为真。a.equals(a)
must always be true. a.equals(a)
必须始终为真。a.equals(b)
and b.equals(c)
, a.equals(c)
must be true.a.equals(b)
和b.equals(c)
, a.equals(c)
必须为真。a.equals(b)
, a.hashCode() == b.hashCode()
must be true.a.equals(b)
, a.hashCode() == b.hashCode()
必须为真。a.hashCode() == b.hashCode()
, that doesn't mean a.equals(b)
, and hashset does not require it. a.hashCode() == b.hashCode()
,那并不意味着a.equals(b)
,并且 hashset 不需要它。return 1;
return 1;
is a legal implementation of hashCode.set.containsKey(k)
which ordinarily takes constant time, will take linear time instead if your objects are all not-equal but have the same hashCode.set.containsKey(k)
如果您的对象不相等但具有相同的哈希码,则将花费线性时间。 Hence, do try to ensure hashcodes are as different as they can be. Breaking any of the above rules does not, generally, result in a compiler error.违反上述任何规则通常不会导致编译器错误。 It often doesn't even result in an exception.
它通常甚至不会导致异常。 But instead it results in bizarre behaviour with hashsets and hashmaps: You put an k/v pair in the map, and then immediately ask for the value back and you get
null
back instead of what you put in, or something completely different.但相反,它会导致散列集和散列映射出现奇怪的行为:你在 map 中放入一个 k/v 对,然后立即请求返回值,你得到
null
而不是你放入的值,或者完全不同的东西。 Just an example.只是一个例子。
NB: One weird effect of all this is that you cannot add equality-affecting state to subclasses , unless you apply a caveat that most classes including all classes in the core libraries don't apply.注意:所有这一切的一个奇怪效果是您不能将影响相等的 state 添加到子类,除非您应用一个警告,即大多数类(包括核心库中的所有类)都不适用。
Imagine as an example that we invent the notion of a 'coloured' arraylist. You could have a red '["Hello", "World"]' list, and a blue one:举个例子,我们发明了“彩色”arraylist 的概念。您可以有一个红色的“["Hello", "World"]' 列表和一个蓝色的列表:
class ColoredArrayList extends ArrayList {
Color color;
public ColoredArrayList(Color c) {
this.color = color;
}
}
You'd probably want an empty red list to not equal an empty blue one.您可能希望一个空的红色列表不等于一个空的蓝色列表。 However, that is impossible if you intend to follow the rules.
但是,如果您打算遵守规则,那是不可能的。 That's because the equals/hashCode impl of
ArrayList
itself considers any other list equal to itself if it has the same items in the same order.这是因为
ArrayList
的 equals/hashCode impl 本身认为任何其他列表等于它自己,如果它具有相同顺序的相同项目。 Therefore:所以:
List<String> a = new ArrayList<String>();
ColoredList<String> b = new ColoredList<String>(Color.RED);
a.equals(b); // this is true, and you can't change that!
Therefore, b.equals(a)
must also be true (your impl of equals
has to say that an empty red list is equal to an empty plain arraylist), and given that an empty arraylist is also equal to an empty blue one, given that a.equals(b)
and b.equals(c)
implies that a.equals(c)
, a red empty list has to be equal to a blue empty list.因此,
b.equals(a)
也必须为真(你的equals
的含义必须说一个空的红色列表等于一个空的普通数组列表),并且给定一个空的 arraylist 也等于一个空的蓝色列表,给定a.equals(b)
和b.equals(c)
意味着a.equals(c)
,红色空列表必须等于蓝色空列表。
There is an easy solution for this that brings in new problems, and a hard solution that is objectively better.有一个简单的解决方案会带来新的问题,也有一个客观上更好的硬解决方案。
The easy solution is to define that you can't be equal to anything except exact instances of yourself, as in, any subclass is insta-disqualified.简单的解决方案是定义除了你自己的确切实例之外,你不能等于任何东西,因为任何子类都是 insta-disqualified。 Imagine
ArrayList
's equals method returns false if you call it with an instance of a subclass of ArrayList. Then you could make your colored list just fine.想象一下,如果您使用 ArrayList 的子类实例调用它,
ArrayList
的 equals 方法将返回 false。然后您就可以使您的彩色列表很好。 But, this isn't necessarily great, for example, you probably want an empty LinkedList
and an empty ArrayList
to be equal.但是,这不一定很好,例如,您可能希望一个空的
LinkedList
和一个空的ArrayList
相等。
The harder solution is to introduce a second method, canEqual
, and call it.更难的解决方案是引入第二种方法
canEqual
并调用它。 You override canEqual
to return 'if other is instanceof
the nearest class in my hierarchy that introduces equality-relevant state'.您覆盖
canEqual
以返回“如果 other 是我的层次结构中最近的 class instanceof
,它引入了与平等相关的状态”。 Thus, your ColoredList should have @Override public boolean canEqual(Object other) { return other instanceof ColoredList; }
因此,你的 ColoredList 应该有
@Override public boolean canEqual(Object other) { return other instanceof ColoredList; }
@Override public boolean canEqual(Object other) { return other instanceof ColoredList; }
. @Override public boolean canEqual(Object other) { return other instanceof ColoredList; }
。
The problem is, all classes need to have that and use it, or it's not going to work, and ArrayList
does not have it.问题是,所有类都需要有它并使用它,否则它不会工作,而
ArrayList
没有它。 And you can't change that.你无法改变这一点。
Project Lombok can generate this for you if you prefer.如果您愿意, Project Lombok可以为您生成它。 It's not particularly common;
这不是特别常见; I'd only use it if you really know you need it.
只有当你真的知道你需要它时,我才会使用它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.