7

我正在用 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 方法必须产生相同的整数结果。

比较floats 和doubles with==进行数值比较,因此+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)总是错误的,但也许这并不正确。另一方面,我可以在 equals 方法比较的地方做什么Double和做什么,但仍然协调不同的 NaN 位模式,然后 let ,所以我得到:Float+0.0 != -0.0NaN == 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. 但无论如何,我真的不需要将我的复数放在哈希图中——我只想做正确的事情,遵循最佳实践等。现在我很困惑。谁能告诉我最好的方法?

4

3 回答 3

1

我已经考虑了更多。问题源于试图平衡 equals 的两种用途:IEEE 754 算术比较和对象/哈希表比较。对于浮点类型,由于 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 方法将坐标与 just 进行比较==,因此this.equals(this)可能为假。同时,它的 hashCode 方法使用doubleToLongBits,因此即使 equals 返回 true,它的 hashCode 对于两个对象也可以不同。文档没有提到细微之处,这意味着问题并不重要:人们不会将这些类型的元组放在哈希表中!(如果他们这样做也不会很有效,因为您必须获得完全相同的数字才能获得相等的密钥。)

在像复数类这样的浮点元组中,equals 和 hashCode 最简单的正确实现是根本不覆盖它们。如果您希望方法考虑值,那么正确的做法是:在两种方法中都Double使用doubleToLongBits(或)。floatToLongBits如果这不适合算术,则需要单独的方法;也许是equals(Complex other, double epsilon)为了在一个公差范围内比较数字是否相等。

请注意,您可以equals(Complex other)在不干扰 的情况下进行覆盖equals(Object other),但这似乎太混乱了。

于 2013-09-18T13:41:26.437 回答
0

或者,hashCode 合约保证如果对象相等,则 hashCode 相等,但如果对象不同,则不保证 hashcode 不同。所以你可以使用 this.real 作为哈希码,并接受冲突。除非有关于您的库实际遇到的数字分布的先验知识,否则可能无法做得更好:您有 128 位值和 32 位哈希,因此冲突是不可避免的(并且无害,除非您可以表明它们使您对预期数据集的查找感到悲观)。

于 2013-10-02T19:30:31.207 回答
0

病态的情况似乎是0.0 != -0.0,所以我会确保这永远不会发生,并按照 Joshua Bloch 在“Effective Java”中告诉你的方式来完成剩下的事情。

于 2013-09-17T17:49:55.597 回答