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

时间:2017-03-14 11:41:04

标签: java equals hashcode

当我有这样的课程时

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总是包含相同的对象类型...我从未创建过List并开始将Integer与String混合。

@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);
}

忽略不常见的情况并抛出异常是不好或好的做法?我认为在比较完整的不同类时,这通常是一个错误。

2 个答案:

答案 0 :(得分:7)

Array.from()var days = [...Array((new Date).getDate())].map((e, i) => i + 1) console.log(days)抛出异常是非常糟糕的做法(我建议您阅读Effective Java了解详细信息)。

此外,您的方法也不必要地复杂。从Java 7开始,这几乎就是编写这些方法的规范方法:

equals()

答案 1 :(得分:-2)

如何缩短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
}

现在,如果ClassCastException不是obj类型,则会获得MyClass。只要您知道如何实现它,这就有效,因为签名中没有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

我想说,这个例子(即子类)不是 uncommon 错误,但它失败了。如果您在签名中添加了例外,它将再次超载,并且您将返回到您开始的位置。你看,由于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 {

}