Java中的hashCode和equals方法中是否允许和可接受异常?

14
一些由框架填充的类(如beans)。因此,不能保证所有字段都设置了。
例如:被标记为@Entity的类通常具有Integer id字段。hashCode可以写成:
public int hashCode() {
    return id.hashCode();
}

但防御性的代码可能看起来像:
public int hashCode() {
    return (id != null) ? id.hashCode() : 0;
}

我需要在hashCodeequals函数中编写检查null或用try { ... } catch (Exception e)包围代码吗?

我没有为这种情况下的防御性编程提供论据,因为它会隐藏将不一致的对象放入集合并导致迟到的错误。我的立场是否有误?

更新 我编写了这样的代码:

import java.util.*;

class ExceptionInHashcode {

    String name;

    ExceptionInHashcode() { }
    ExceptionInHashcode(String name) { this.name = name; }

    public int hashCode() {
        // throw new IllegalStateException("xxx");
        return this.name.hashCode();
    }

    public static void main(String args[]) {
        Hashtable list = new Hashtable();
        list.put(new ExceptionInHashcode("ok"), 1);
        list.put(new ExceptionInHashcode(), 2); // fail
        System.out.println("list.size(): " + list.size());
    }
}

并运行它:

java -classpath . ExceptionInHashcode
Exception in thread "main" java.lang.NullPointerException
        at ExceptionInHashcode.hashCode(ExceptionInHashcode.java:12)
        at java.util.Hashtable.hash(Hashtable.java:262)
        at java.util.Hashtable.put(Hashtable.java:547)
        at ExceptionInHashcode.main(ExceptionInHashcode.java:18)

我认为如果对象处于错误状态,早期发现错误比返回零更好...


1
你的 hashCode 实现破坏了 Object#hashCode 的约定:在 Java 应用程序执行期间,每当它在同一对象上被多次调用时,只要在 equals 比较中使用的对象信息未被修改,hashCode 方法必须始终返回相同的整数。这个整数不需要在同一应用程序的不同执行之间保持一致。 - Luiggi Mendoza
5
如果你需要抛出一个异常,我更倾向于抛出IllegalStateException而不是让NullPointerException直接冒泡。对我来说,在别人的代码中遇到NullPointerException就像嗅到了一个错误,但IllegalStateException告诉我“按照设计,你不允许这样做(尚未)”。 - lc.
2
@LuiggiMendoza:如果ID发生更改,用于等式比较的信息确实会发生变化。你是在谈论返回的数字还是异常? - Jon Skeet
没问题,这在你所提到的合同中已经明确规定:“只要对象上用于等式比较的信息没有被修改,hashCode方法必须始终返回相同的整数。”由于用于等式比较的信息已被修改,因此哈希码发生变化是可以接受的。 - Jon Skeet
4
是的,它并没有违反“hashCode”的规则。如果你在将其放入映射表后更改“id”,那么你就违反了对键进行操作的规则。 - Jon Skeet
显示剩余10条评论
4个回答

8
我个人会检查是否为空,并使方法始终返回无异常。虽然运行时异常通常没有记录,并且可以在任何地方抛出,但我认为它们被equalshashCode抛出通常是不合适的。一方面,我肯定可以理解您关于在完全填充之前将其放入映射中的观点...但另一方面,很难真正知道equals将在何处被调用。就像lc在评论中所说,如果您真的想抛出异常,最好抛出IllegalStateException以清楚地表明这是故意的,而不是让NullReferenceException“默认”抛出,这使得看起来像您没有考虑到空场景。

2
我同意,在这里使用IllegalStateException是最合适的。 - Andremoniy
我个人很少检查 null,除非我正在验证一个分支没有遍历但根据方法合同我应该抛出 NPE 的参数。是的,这可能是唯一的例外。NPE 是上帝赐给我们的礼物,只要我们允许代码在 null 成为问题的 何时哪里 精确地崩溃。没有 ID = NPE。然而,我会对其他方面使用 IllegalStateException,比如匹配的 ID 但不匹配 version(请参见此答案)。 - Martin Andersson

8
一般来说,答案是“这取决于具体情况”。
- 如果你永远不应该看到该字段为null的类实例,则允许抛出NPE是合理的。NPE表示存在错误;即您的非正式不变量被破坏的情况。 - 如果有可能出现一个具有null的实例,则应在不抛出异常的情况下处理null情况。
在这种特殊情况下,您显然正在处理对象,其中如果尚未持久化对象,则id字段可以为null。这提出了一个棘手的问题:
- 如果您不允许id为null,则必须小心不要将非持久对象放入哈希表中。 - 如果允许id为null,则会出现以下问题:如果将对象添加到哈希表中,然后再持久化它,则hashcode可能会更改,导致哈希表损坏。因此,现在您需要通过在瞬态字段中备忘对象的hashcode值来防御它。并且当对象持久化时,等同性发生变化时,大致相同的问题也会发生。因此,最好不要在同一个哈希表中混合已持久化和未持久化的键。 考虑到所有这些因素,我建议要么抛出NPE,要么不在equals / hashcode中使用id字段。

我完全同意你所说的一切,除了第一句话哈哈。对于这个“问题”的答案真的不是“取决于情况”吗?因为...抛出异常是被允许并且被接受的。所以答案是“是”?嗯...也许这有点纠结哈哈哈。感谢Stephen提供如此棒的答案!我在这里也有一个类似的回答。 - Martin Andersson
1
是的,您在抓住细节问题。这个问题问的是“可接受”的,而不是“被接受”。我不是在谈论Java编译器的接受程度(当然,编译器可以接受。这很明显!)我是在谈论那些将要审查OP代码的人的接受程度。一些人可能会说:“但null是字段'x'的一个允许值,您的hashCodeequals方法不应该出错”。 - Stephen C

2

为验证对象的状态,您应该使用Bean验证框架来确保对象的状态是有效的。

不应该在hashcode和equals方法中抛出异常。

equals方法必须检查空值。当创建对象时,创建者有责任确保对象处于有效状态,因此hashCode永远不会抛出异常。为此,可以使用bean验证。在我看来。

更新:由于您正在使用为您创建bean的bean框架,因此必须依赖bean验证。但是,否则必须由创建对象的Factory负责确保只创建有效实例


1
你的代码中永远不应该出现空指针异常。尤其是在经常被自己代码以外的函数使用的情况下。"hashcode","equals"和"toString"方法都不应该抛出异常。
顺便提一句:你可以直接将id作为hashcode返回。

如果“id”是一个具有自己的hashCode()方法的对象,那么它显然不能返回“id”作为hashCode。 - user207421
@EP id 是一个整数,正如帖子中所述。如果对象不为空,则可以返回其值,并且它将是一个有效的 hashCode,满足所有 hashCode 的要求。 - Dariusz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接