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

时间:2009-03-11 21:03:29

标签: java xml equals graph-theory

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

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

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

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

知道如何去做吗?

编辑:我知道添加罐子是正常的做事方式。我知道罐子是可重复使用的单位。然而,(在我的项目中)所需的官僚机构并不能证明结果是合理的 - 我会继续调试并踩到。

10 个答案:

答案 0 :(得分:5)

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

尝试hamcrest matchers - 您可以在“全部”匹配器中编写每个匹配器:

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的值为“value”但是“不同的值”'

当然,您可以内联静态工厂。这比使用apache-commons EqualsBuilder稍微重一点,但它确实能准确描述失败的确切内容。

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

BTW,hamcrest基本jar是32K(包括源码!),让你可以选择查看代码并对你的老板说“我会坚持这个作为我自己的代码”(我认为这是你的导入问题)

答案 1 :(得分:3)

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

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

答案 2 :(得分:2)

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

答案 3 :(得分: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中使用条件断点。不幸的是,它使代码变得更加丑陋。

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

答案 4 :(得分:1)

  

我不介意复制一个班级,   甚至包裹,进入我的测试区域   并在那里使用它,但导入一个   对于这个整个罐子是不会的   发生。

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

至于你的具体问题,你是否有很多不同的类使用许多不同的属性来确定相等性,或者你只是有一个基本上相同类的深度嵌套的对象图?在后一种情况下,只需构造equals()方法就可以了,这样就可以在“return false”语句中加入断点。在前一种情况下,我认为这可能是太多的工作。但是,基于XML的比较可能也不起作用,因为它将显示语义上相等的对象(例如集合和地图)之间的差异。

答案 5 :(得分:1)

鉴于你的项目不能添加一个jar,看起来完全没有SO答案给出一个解决方案的整个实现,其他项目需要大量的代码才能完成(并且很好地包含在Jar中)

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

答案 6 :(得分:0)

  

我知道添加罐子是正常的做事方式。我知道罐子是可重复使用的单位。然而,(在我的项目中)所需的官僚机构并不能证明结果是合理的 - 我会继续调试并踩到。

这方面的一个方法就是包含一个像Spring这样的库(其中包含其他jar的内容)我看过Spring项目实际上并没有使用任何Spring jar,所以他们可以使用任何捆绑它的jar。

答案 7 :(得分:0)

commons-jxpath可能对快速检查对象树很有用。我不完全理解包含jar的问题,但你可以在你自己的项目中使用它,无论你使用什么IDE,大概都允许你在调试时使用表达式。

答案 8 :(得分:0)

也许关于方法跟踪的这个article会对你有帮助。

  

工作原理

     

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

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

  

isApplet =假

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

  

%java -jar /home/mike/java/trace.jar -classpath“/home/mike/jdk1.3/demo/jfc/SwingSet2/SwingSet2.jar”-exclude CodeViewer SwingSet2
  | SwingSet2()
。   | SwingSet2
。   | SwingSet2.main([Ljava.lang.String; @ 1dd95c)
  || isApplet(SwingSet2 @ 3d12a6)
  || isApplet =假
  || 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 =假
  |||的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 =假
  ..showSplashScreen
  .RUN
  || initializeDemo(SwingSet2 @ 3d12a6)
  ||| createMenus(SwingSet2 @ 3d12a6)
  |||| getString(SwingSet2 @ 3d12a6,“MenuBar.accessible_description”)
  ||||| getResourceBundle(SwingSet2 @ 3d12a6)
  |||||getResourceBundle=java.util.PropertyResourceBundle@6989e
  |||| getString =“Swing演示菜单栏”

答案 9 :(得分:0)

XMLEncoder只编码bean属性,而equals显然可以在非bean和任何内部字段上工作。

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

所以我认为没有bytecode instrumentation可以实际修改类equals()函数以查看它访问的字段。即便如此,“真正”知道函数返回false的原因仍然非常困难。但希望这是一个比较equals()

中实际访问的字段的简单问题