1

如何找出导致 equals() 返回 false 的原因?

我不是在问一个确定的、永远正确的方法,而是在问一些有助于开发过程的东西。目前我必须进入 equals() 调用(通常是它们的树),直到其中一个为假,然后进入它,令人作呕。

我考虑过使用对象图,将其输出到 xml 并比较两个对象。但是,XMLEncoder 需要默认构造函数,jibx 需要预编译,我的项目中没有使用 x-stream 和 simple api。我不介意将单个类甚至一个包复制到我的测试区域并在那里使用它,但是为此导入整个 jar 是不会发生的。

我还考虑过自己构建一个对象图遍历器,我可能仍然会这样做,但我不想开始处理特殊情况(有序集合、非有序集合、地图......)

知道该怎么做吗?

编辑:我知道添加罐子是正常的做事方式。我知道罐子是可重复使用的单元。但是,为此(在我的项目中)需要的官僚机构并不能证明结果是合理的——我会继续调试并介入。

4

10 回答 10

5

这可能不是一个完整的图表比较......除非你的 equals 包括每个类中的每个属性......(你可以尝试 == :))

尝试hamcrest 匹配器- 您可以将每个匹配器组合在“全部”匹配器中:

Matcher<MyClass> matcher = CoreMatchers.allOf(
  HasPropertyWithValue.hasProperty("myField1", getMyField1()),
  HasPropertyWithValue.hasProperty("myField2", getMyField2()));
if (!matcher.matches(obj)){
  System.out.println(matcher.describeFailure(obj));
  return false;
}
return true;

它会说:'期望 myField1 的值为“值”,但为“不同的值”'

当然你可以内联静态工厂。这比使用apache-commons EqualsBuilder要重一些,但它确实可以准确地描述失败的原因。

您可以创建自己的专用匹配器来快速创建这些表达式。在这里复制apache-commons EqualsBuilder是明智的。

顺便说一句,hamcrest 基本 jar 为 32K(包括源代码!),您可以选择查看代码并向您的老板说“我会坚持将其作为我自己的代码”(我认为这是您的导入问题)。

于 2009-03-11T23:37:32.423 回答
3

您可以使用方面在对象图中的类上修补“相等”,并使它们在返回 false 时将对象状态记录到文件中。要记录对象状态,您可以使用 beanutils 之类的东西来检查对象并转储它。这是一种基于 jar 的解决方案,可以在您的工作空间中轻松使用

如果存储在树中的对象的层次结构足够简单,则可以在类“equal”实现上放置条件断点,仅在“equal”返回 false 时触发,这将限制您必须进入的次数...您可以在具有调试器访问权限的任何地方使用它。eclipse 处理得很好。

于 2009-05-04T16:24:10.940 回答
2

听起来您想要java-diff或类似的东西。

于 2009-03-11T21:06:50.040 回答
2

好吧,这是一个完全奇怪的看待它的方式,但是引入一个新的静态方法怎么样:

public static boolean breakableEquals(Object o1, Object o2)
{
    if (o1 == o2)
    {
        return true;
    }
    if (o1 == null || o2 == null)
    {
        return false;
    }
    // Don't condense this code!
    if (o1.equals(o2))
    {
        return true;
    }
    else
    {
        return false;
    }
}

我知道,最后一点看起来很疯狂......但不同的是你可以在“return false”上设置断点。如果您随后breakableEquals在所有深度相等比较中使用,那么您可以在点击第一个“ return false”时立即中断。

诚然,如果您要比较许多原始值,这无济于事……但这可能会有所帮助。我不能说我曾经实际使用过这个,但我不明白为什么它不起作用。当然,它的效率会稍低一些——因此,如果您正在处理高性能代码,您可能希望事后对其进行更改。

另一种选择是使用类似的东西:

boolean result = // comparison;
return result;

假设您的 IDE 支持它们,那么您可以在 return 语句上放置一个条件断点,并将条件设置为“ !result”。

还有一个选择:

public static boolean noOp(boolean result)
{
    return result;
}

然后,您可以在比较中使用它:

return Helpers.noOp(x.id == y.id) &&
       Helpers.noOp(x.age == y.age);

我希望当您不调试时,这会被 JIT 优化掉——但同样,您可以在noOp. 不幸的是,它使代码更丑陋。

简而言之:这里没有特别吸引人的解决方案,而只是一些在某些情况下可能会有所帮助的想法。

于 2009-05-10T08:18:55.473 回答
1

我不介意将单个类甚至一个包复制到我的测试区域并在那里使用它,但是为此导入整个 jar 是不会发生的。

嗯什么?将 jar 添加到类路径(如果有的话)比复制类或整个包作为源代码更容易且对项目的干扰更少。

至于您的具体问题,您是否有很多不同的类使用许多不同的属性来确定相等性,或者您只是具有本质上相同类的深层嵌套对象图?在后一种情况下,很容易构建 equals() 方法,以便您可以在“return false”语句上放置断点。我想,在前一种情况下,这可能工作量太大了。但是,基于 XML 的比较也可能不起作用,因为它会显示语义相同的对象(例如 Set 和 Map)之间的差异。

于 2009-03-11T23:54:44.707 回答
1

鉴于您的项目无法添加 jar,它似乎远远超出了 SO 的答案,以提供其他项目需要大量代码才能完成的解决方案的完整实现(并且很好地包含在您的 Jar 中)。

非代码解决方案如何——调试器中的条件断点?您可以添加仅在方法返回 false 时触发的断点,并将它们放在所有相关类上。没有踩踏。

于 2009-05-07T16:08:42.730 回答
0

我知道添加罐子是做事的正常方式。我知道罐子是可重复使用的单元。但是,为此(在我的项目中)需要的官僚机构并不能证明结果是合理的——我会继续调试并介入。

解决这个问题的一种方法是包含一个像 Spring 这样的库(它会拉入其他 jar 的负载)我已经看到 Spring 项目实际上并没有使用任何 Spring jar,只是这样他们就可以使用与其捆绑的任何 jar。

于 2009-05-03T10:26:38.390 回答
0

commons-jxpath可能有助于快速检查对象树。我不完全理解包含 jars 的问题,但您可以在您自己的项目中使用它,在您使用的任何 IDE 中,大概可以让您在调试期间使用表达式。

于 2009-05-06T20:41:16.077 回答
0

也许这篇关于方法跟踪的文章会对你有所帮助。

这个怎么运作

自定义类加载器读取类文件并使用跟踪代码检测每个方法。类加载器还为每个类添加了一个静态字段。该字段有两种状态,“开”和“关”。跟踪代码在打印之前检查此字段。命令行选项访问和修改此静态字段以控制跟踪输出。

他们显示的示例输出看起来很有希望解决您的问题,因为它显示了某些方法(如 isApplet)的返回值:

isApplet=false

您应该很容易发现在 equals 中开始返回 false 的确切类。这是页面的完整示例输出:

% java -jar /home/mike/java/trace.jar -classpath "/home/mike/jdk1.3/demo/jfc/SwingSet2/SwingSet2.jar" -排除 CodeViewer SwingSet2
|SwingSet2.()
|SwingSet2。
|SwingSet2.main([Ljava.lang.String;@1dd95c)
||isApplet(SwingSet2@3d12a6)
||isApplet=false
||SwingSet2.createFrame(apple.awt.CGraphicsConfig@93537d)
||SwingSet2.createFrame=javax. swing.JFrame@cb01e3
||createSplashScreen(SwingSet2@3d12a6)
|||createImageIcon(SwingSet2@3d12a6, "Splash.jpg", "Splash.accessible_description")
|||createImageIcon=javax.swing.ImageIcon@393e97
|||isApplet (SwingSet2@3d12a6)
|||isApplet=false
|||getFrame(SwingSet2@3d12a6)
|||getFrame=javax.swing.JFrame@cb01e3
|||getFrame(SwingSet2@3d12a6)
|||getFrame=javax.swing.JFrame@cb01e3
||createSplashScreen
.run(SwingSet2$1@fba2af)
..showSplashScreen(SwingSet2@ 3d12a6)
...isApplet(SwingSet2@3d12a6)
...isApplet=false
..showSplashScreen
.run
||initializeDemo(SwingSet2@3d12a6)
|||createMenus(SwingSet2@3d12a6)
||||getString(SwingSet2@3d12a6, " MenuBar.accessible_description")
|||||getResourceBundle(SwingSet2@3d12a6)
|||||getResourceBundle=java.util.PropertyResourceBundle@6989e
||||getString="Swing 演示菜单栏"

于 2009-05-07T00:31:44.993 回答
0

XMLEncoder 只对 bean 属性进行编码,而 equals 显然可以处理非 bean 和任何内部字段。

部分问题是您不知道 equals 实际在看什么。一个对象可能有许多不同的字段,但仍然声称它与其他对象相等,甚至可能是不同的类型。(例如,自定义 URL 类可能会为等于其外部形式的字符串返回 true)。

因此,我认为没有字节码检测是不可能的,您可以在其中实际修改类 equals() 函数以查看它访问的字段。即便如此,要“真正”知道为什么函数返回 false 仍然非常困难。但希望这将是一个比较在 equals() 中实际访问的字段的简单问题

于 2009-05-09T05:17:38.510 回答