Apache Commons ObjectUtils equals方法测试哪种类型的相等性?

18

我一直认为Java中有两种类型的相等性:

  • 值相等:使用.equals()方法测试两个对象在非空对象引用上实现等价关系。
  • 引用相等:使用==运算符测试两个原始类型或内存位置是否相等。

以下页面更详细地描述了这些语言基础知识。

这些链接中没有明确说明,如果比较两个null对象引用的值相等会发生什么。隐含的假设是应该抛出NullPointerException,但ObjectUtils.equals()方法并不会这样做,它可能被认为是最佳实践的实用方法。
让我担心的是Apache Commons似乎已经通过后门有效地引入了Java的第三种相等度量,并且已经混乱的情况可能变得更加复杂。我称之为第三种相等度量,因为它尝试测试值相等性,当失败时则退而求其次,测试引用相等性。Apache Commons的相等性测试与值相等性和引用相等性有许多相似之处,但也有明显的不同。
我是否有理由担心并尽可能避免使用ObjectUtils.equals()
是否有理由认为ObjectUtils.equals()提供了其他两种相等度量的有用结合?

选定答案

这个问题似乎没有共识意见,但我决定将Bozho的答案标记为正确,因为他最好地引起了我的注意,看到了空安全相等检查的最大问题。我们所有人都应该编写能够快速失败的代码,解决为什么要比较两个空对象的值相等的根本原因,而不是试图掩盖问题。

1
对于一个经过深入研究的问题,给予+1的赞同。 - trashgod
你能解释一下这是第三种等式的原因吗?我认为比较两个空的“内存位置”并得到true与比较两个基本的0值并得到true没有什么区别。NullPointerException是尝试取消引用空指针...完全不同于仅检查其值。 - PSpeed
我已更新问题,明确说明了Apache Commons相等测试如何与测试相等性的两种Java方法不同,但您的假设忽略了更微妙的问题。 当我测试值相等性时,我并不会比较两个空内存位置。 JLS非常明确地说明了这一点。内存位置有时会被比较,这是一种实现细节/快捷方式。它没有被指定,并且在两个对象都为null的情况下是不适当的。在Java中,null不是一个对象。 - Caoilte
但是不清楚的是,为什么比较两个“不是对象”的东西就不应该相等。在这种情况下,返回true似乎是合乎逻辑的...特别是当最可能的比较是两个对象的字段值时。如果A.foo == B.foo,即使foo指向“不是对象”,对象A.equals()对象B。 - PSpeed
3个回答

12

这是 ObjectUtils.equals(..) 的代码:

public static boolean equals(Object object1, Object object2) {
     if (object1 == object2) {
       return true;
     }
     if ((object1 == null) || (object2 == null)) {
       return false;
    }
    return object1.equals(object2);
}

ObjecUtils文档 明确说明传递的对象可能是 null。

现在关于当你比较两个null时是否应该返回true的问题。我认为不应该,因为:

  • 当你比较两个对象时,你可能会稍后对它们进行一些操作。这将导致NullPointerException
  • 将两个null传递给compare意味着它们来自某个地方而不是“真正”的对象,可能由于某些问题。在这种情况下,仅比较它们是错误的-程序流应该在此之前停止。
  • 在我们这里使用的自定义库中,有一个名为equalOrBothNull()的方法-它在null比较方面与此实用程序中的equals方法不同。

这是不正确的。System.out.println("Is null == null? " + (null == null)); 输出 true - matt b
我同意ObjecUtils文档非常清楚地说明了它们的作用。我对它们的担忧更多的是它们没有警告你它们对等式的定义与JLS中的两个不同。我最担心的是这种“宽松”的相等检查会掩盖底层错误。 - Caoilte
1
如果A.equals(B)为false,而A == B为true,则存在非常大的问题。但在某些情况下,当引用实际上是相同的时,检查A == B可以快上数千倍。我想不出任何一个需要调用者首先进行空检查的用例...然而,我所需求的每个这种util方法的用例都需要能够传递nulls。 - PSpeed
1
@Bozho,你能否考虑添加对Spring的nullSafeEquals的引用? - Adam Gent
@Bozho,我们可以使用它来替代Equalsbuilder吗? - kaushik
显示剩余4条评论

5

我是否有理由担心并希望尽可能避免使用ObjectUtils.equals()呢?

不是的。您需要考虑equals取决于您的要求。想要将两个null视为相等,将任何非null视为与null不相等而不必处理NullPointerException是一个非常普遍的要求(例如,当您想从setter触发值更改事件时)。

实际上,这就是equals()通常应该工作的方式,通常情况下,这种行为的一半已经被实现了(Object.equals()的API文档指出:“对于任何非null引用值x,x.equals(null)应返回false。”)-它不起作用的另一方面主要是由于技术限制(该语言设计时没有多分派以使其更简单)。


1
如果您对此有所担忧,那么您可以选择:1)不使用这种方法;2)编写自己的方法来包装它。
public class MyObjectUtils {
    public static boolean equals(Object obj1, Object obj2) {
        return obj1 != null && obj2 != null && ObjectUtils.equals(obj1, obj2);
    }
}

在我看来,允许 null 等于 null 似乎很奇怪,但这似乎不是一个大问题。在大多数情况下,如果一个或多个对象为空,我甚至不希望我的应用程序进入涉及相等性测试的代码路径。

嗯,(就像我在最初错误的陈述中所说的那样),(null == null) = true 可能成为后续空指针异常的一个潜在原因。 - Bozho
让我来重新表述一下,对于两个引用都为 null 的情况进行相等性测试似乎很奇怪。 - matt b
让我们看看...我在哪些地方将null与null进行了比较:几乎每个我编写的具有非基本字段对象的.equals()方法,每个我编写的PropertyChangeEvent分发...天啊,它总是如此频繁出现。 - PSpeed
当然有简单的解决方法。我的担忧是,作为最佳实践实用程序库,Apache Commons 有能力影响开发人员,在这种情况下,结果可能对下游代码的质量产生负面影响。我为许多不同的客户工作,处理许多不同的代码库,其中许多在任何可以使用 Apache Commons 的地方都会使用它,而不停下来思考他们是否应该这样做。 - Caoilte
1
实用程序代码旨在减少人们必须进行的剪切和粘贴操作。在这种情况下,我所使用过的所有实用方法用例都需要我添加一个额外的检查。实际上,在上面的示例方法之上,我还必须添加一个 obj1 == obj2(涵盖我的另一个用例),因为当引用相同时,有时速度快1000倍。 - PSpeed

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