我正在用Java创建一个复杂的数字类:
public class Complex {
public final double real, imag;
public Complex(double real, double imag) {
this.real = real;
this.imag = imag;
}
... methods for arithmetic follow ...
}
我实现了这样的equals方法:
@Override
public boolean equals(Object obj) {
if (obj instanceof Complex) {
Complex other = (Complex)obj;
return (
this.real == other.real &&
this.imag == other.imag
);
}
return false;
}
但是如果你重写equals,你也应该覆盖hashCode。其中一条规则是:
如果两个对象根据equals(Object)方法相等,则对两个对象中的每一个调用hashCode方法必须产生相同的整数结果。
将float
和double
与==
进行比较会进行数值比较,因此+0.0 == -0.0
和NaN值不等于包括它们在内的所有内容。所以我尝试实现hashCode方法来匹配equals方法,如下所示:
@Override
public int hashCode() {
long real = Double.doubleToLongBits(this.real); // harmonize NaN bit patterns
long imag = Double.doubleToLongBits(this.imag);
if (real == 1L << 63) real = 0; // convert -0.0 to +0.0
if (imag == 1L << 63) imag = 0;
long h = real ^ imag;
return (int)h ^ (int)(h >>> 32);
}
但后来我意识到,如果任何一个字段是NaN,这在哈希映射中会有奇怪的效果,因为this.equals(this)
总是为假,但也许这不是错误的。另一方面,我可以执行Double
和Float
所做的事情,其中equals方法比较+0.0 != -0.0
,但仍然协调不同的NaN位模式,并让NaN == NaN
,所以我得到了:
@Override
public boolean equals(Object obj) {
if (obj instanceof Complex) {
Complex other = (Complex)obj;
return (
Double.doubleToLongBits(this.real) ==
Double.doubleToLongBits(other.real) &&
Double.doubleToLongBits(this.imag) ==
Double.doubleToLongBits(other.imag)
);
}
return false;
}
@Override
public int hashCode() {
long h = (
Double.doubleToLongBits(real) +
Double.doubleToLongBits(imag)
);
return (int)h ^ (int)(h >>> 32);
}
但是如果我这样做那么我的复数就不像真实数字那样+0.0 == -0.0
。但我真的不需要将我的复杂数字放在哈希映射中 - 我只想做正确的事情,遵循最佳实践等等。现在我只是感到困惑。有人可以告诉我最好的方法吗?
答案 0 :(得分:1)
我已经考虑过这个了。问题源于试图平衡两种equals的使用:IEEE 754算术比较和Object / hashtable比较。对于浮点类型,由于NaN,永远不能满足这两个需求。算术比较需要NaN != NaN
,但是对象/哈希表比较(等于方法)需要this.equals(this)
。
Double
根据Object
的合同正确实施方法,因此NaN == NaN
。它也+0.0 != -0.0
。这两种行为与原始浮点数/双精度类型的比较相反。
java.util.Arrays.equals(double[], double[])
以与Double
(NaN == NaN
,+0.0 != -0.0
)相同的方式对元素进行比较。
java.awt.geom.Point2D
在技术上是错误的。它的equals方法将坐标与==
进行比较,因此this.equals(this)
可能为false。同时,它的hashCode方法使用doubleToLongBits
,因此即使equals返回true,它的hashCode对于两个对象也可以不同。 doc没有提到细微之处,这意味着问题并不重要:人们不会将这些类型的元组放在哈希表中! (如果他们这样做会不会很有效,因为你必须得到完全相同的数字才能得到一个相同的密钥。)
在像复数类这样的浮点元组中,equals和hashCode的最简单正确实现是不完全覆盖它们。
如果您希望方法在帐户中获取值,那么正确的事情是Double
所做的:在两种方法中使用doubleToLongBits
(或floatToLongBits
)。如果那不适合算术,则需要单独的方法;或许equals(Complex other, double epsilon)
来比较公差范围内的相等数字。
请注意,您可以覆盖equals(Complex other)
而不会干扰equals(Object other)
,但这似乎太令人困惑了。
答案 1 :(得分:0)
病态案例似乎是0.0 != -0.0
,所以我确保永远不会发生,并完全按照Joshua Bloch在“Effective Java”中告诉你的方式做其余部分。
答案 2 :(得分:0)
或者,如果对象相等,则hashCode契约保证hashCodes相等,但如果对象不同,则hashtring不同。所以你可以使用this.real作为哈希码,并接受冲突。除非有关于您的库实际遇到的数字分布的先验知识,否则可能无法做得更好:您有128位值和32位散列,因此冲突是不可避免的(并且无害,除非您可以表明他们对你期望的数据集的查找感到悲观。)