我应该继续缩短我的 equals 和 hashcode 函数吗?

Should i continue shortening my equals and hashcode functions?

当我有一个这样的class

public class MyClass {
    private ClassA classA;
    private ClassB classB;
}

我的 equals 和 hascode 函数经常以这样的方式结束

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((classB == null) ? 0 : classB.hashCode());
    result = prime * result + ((classA == null) ? 0 : classA.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    MyClass other = (MyClass) obj;
    if (classB == null) {
        if (other.classB != null)
            return false;
    } else if (!classB.equals(other.classB))
        return false;
    if (classA == null) {
        if (other.classA != null)
            return false;
    } else if (!classA.equals(other.classA))
        return false;
    return true;
}

所以我开始忽略有人将苹果与香蕉进行比较。我的应用程序中的 HashSets 或 HashMaps 总是包含相同的对象类型......我从来没有创建一个列表并开始将整数与字符串混合。

@Override
public int hashCode() {
    return classB.hashCode();
}

@Override
public boolean equals(Object obj) {
    MyClass other = (MyClass) obj;
    return other.classB.equals(this.classB) && other.classA.equals(this.classA);
}

忽略不常见的情况并让异常抛出是坏习惯还是好习惯?我认为比较完全不同的 classes 时最常见的错误。

equals()hashCode() 抛出异常是非常糟糕的做法(我鼓励您阅读 Effective Java 了解详细信息)。

此外,您的方法不必要地复杂。自 Java 7 以来,这几乎是编写这些方法的规范方式:

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }else if (o instanceof MyClass) {
        // some will argue that the line above should have a
        // .getClass() check instead of an instanceof check.
        // those people also need to read Effective Java :-)
        MyClass that = (MyClass) o;
        return Objects.equals(this.classA, that.classA)
            && Objects.equals(this.classB, that.classB)
    } else {
        return false;
    } 
}

@Override
public int hashCode() {
    return Objects.hash(classA, classB);
}

缩短 equals() 方法的方式是一种不好的做法(您的 hashCode 方法忽略了 classA,但可能存在适合的用例)。让我解释一下 equals.

的问题

您的明显方法是假设所有 Objects 都是 MyClass 类型。但是 Java 是一种类型安全的语言,因此您应该始终定义您期望的数据类型。否则,您可以简单地在任何地方期望 Objects 并像您在示例中所做的那样投射它们。这可能会像脚本语言(例如,Javascript)一样工作,但是一旦你的代码库变得更大并且多个开发人员试图理解和改进你的代码,它就会变得混乱并且你失去了类型的好处 -安全(例如,在编译期间查看与类型相关的错误)。

您在这里面临的另一个问题是您正在使用 Object 的界面。但是让我们假设您使用正确的数据类型定义了一个额外的(重载)方法,您的代码将如下所示:

public boolean equals(MyClass other) {
    return other.classB.equals(this.classB) && other.classA.equals(this.classA);
}

现在您有了一个完全符合您预期的方法,对吧?不完全:

MyClass myClass1 = new MyClass();
MyClass myClass2 = clone(myClass1)
myClass1.equals(myClass2) // works => true
myClass2.equals(myClass1) // the same
Object myClass3 = (Object) myClass2
myClass3.equals(myClass1) // Compares the address with the default Object.equals() => false
myClass1.equals(myClass3) // the same

您会看到,仅通过更改数据类型,结果就会发生变化,尽管 myClass1myClass2myClass3 非常相等。您不会收到异常或编译错误。好吧,让我们通过实际覆盖该方法(就像您所做的那样)来改变它。

@Override
public boolean equals(Object obj) {
    MyClass other = (MyClass) obj;
    return this.equals(other); // this is the overloaded method
}

现在,如果 obj 不是 MyClass 类型,您将得到一个 ClassCastException。只要您知道如何实现它,它就可以工作,因为签名中没有 Throwable。要考虑的另一件事是 ClassCastException 扩展了 RuntimeException,这对开发人员来说是意想不到的(即未选中)。这意味着如果我不知道它是如何实现的(例如,因为它是已编译库的一部分),或者即使多年后继续开发并且不记得它的人可能会想出一些东西像这样:

public class MyExtraClass extends MyClass {
    private String someIrrelevantStuff;
    // I don't want or need a separate equals
    // and assume that MyClass or Object takes care of it
}

MyClass myClass1 = new MyClass();
MyClass myClass2 = (MyClass) new MyExtraClass();
myClass2.equals(myClass1) // works => false
myClass1.equals(myClass2) // Throws ClassCastException
// Application crashes completely at this point
// just because I didn't expect the unchecked exception

我会说,这个例子(即子类化)不是不常见错误,但它失败了。如果您将异常添加到签名中,它会再次超载,您又回到了开始的地方。你看,就因为Object的接口,没有出路。

但是,有理由问一下这个接口是否仍然适用于Java当前的发行版本。提供默认的 deepEquals() 方法而不是当前的 equalsAddressOf() 是否合适(不要与仅适用于当前的 deepEquals() 混淆数组)? Scala 已经通过其 case classes 解决了这个问题。如果您想要更短的代码,您可以考虑切换到 Scala 或(可选)不安全的语言,如 Python。

毕竟,如果您真的更喜欢 false 状态的区别。您可以简单地使用自己的界面:

public abstract class DeeplyComparableObject extends Object {

    public abstract boolean deeplyEquals(Object o) throws TypeNotEqualException;
}

public class TypeNotEqualException extends Exception {

}