【问题标题】:Deep reflective compare equals深度反射比较等于
【发布时间】:2012-03-26 19:43:14
【问题描述】:

我正在尝试通过将结果对象与原始对象进行比较来验证序列化和反序列化例程。这些例程可以序列化任意和深度嵌套的类,因此我想要一个比较例程,它可以给定原始实例和最终实例,并反思性地遍历每个值类型并比较值并迭代地深入参考类型以比较值。

我已经尝试过 Apache Commons Lang EqualsBuilder.reflectionEquals(inst1, inst2) 但这似乎并没有进行非常深入的比较,它只是比较引用类型的相等性,而不是深入研究它们:

以下代码说明了我的问题。第一次调用reflectionEquals 返回真,但第二次返回假。

有没有人可以推荐的库例程?

class dummy {
    dummy2 nestedClass;
}

class dummy2 {
    int intVal;
}

@Test
public void testRefEqu() {

    dummy inst1 = new dummy();
    inst1.nestedClass = new dummy2();
    inst1.nestedClass.intVal = 2;
    dummy inst2 = new dummy();
    inst2.nestedClass = new dummy2();
    inst2.nestedClass.intVal = 2;
    boolean isEqual = EqualsBuilder.reflectionEquals(inst1.nestedClass, inst2.nestedClass);
    isEqual = EqualsBuilder.reflectionEquals(inst1, inst2);
}

【问题讨论】:

  • 如果反射等于只是比较引用,那么它有一个错误。它应该做得更多。
  • @DwB 我怀疑代码的目的是允许您在特定类中反射性地实现 equals()。这与我想要反映两个对象实例的不同。在这种情况下,这不是错误,而是令人失望!
  • 我失去了半天的时间,因为 EqualsBuilder 这种无证的弱行为。如果传递的对象的字段是非原始的,则构建器只需调用 object.equals()。非常令人失望和无用。
  • @AlexWien 这远非没用。事实上,如果使用不当,完全递归的相等方法可能会非常危险!但是,我同意文档应该更清晰。我提出了issues.apache.org/jira/browse/LANG-1034 来解决缺失的功能。我将提出另一个错误来解决误导性文档。
  • @AlexWien 我从未见过任何名为equals 的方法进行深度比较。深度比较似乎是几乎不需要的例外,因此您不应该对标准行为感到失望。

标签: java serialization reflection comparison equals


【解决方案1】:

从这个问题的答案https://stackoverflow.com/a/1449051/116509 和一些初步测试来看,Unitils 的ReflectionAssert.assertReflectionEquals 似乎符合您的预期。 (编辑:但可能会被放弃,所以你可以试试 AssertJ https://assertj.github.io/doc/#assertj-core-recursive-comparison

2021 年编辑:EqualsBuilder 现在有一个testRecursive 选项。然而,提到的单元测试库会为您提供更好的失败消息来帮助调试,因此根据上下文,它们仍然是最佳选择。

【讨论】:

  • 我和你一样感到惊讶,但为了公平起见 EqualsBuilder,正如我对这个问题的评论,我认为这是对例程意图的误解。也许这可以说得更清楚。
  • 我已经对 assertReflectionEquals 进行了一些初步测试,它似乎确实有效。非常感谢
  • @artbristol 我已经提出issues.apache.org/jira/browse/LANG-1035 来解决这个问题。我会尽量确保它在下一个版本中得到修复。
  • Unitils 似乎不再受支持 (SO)。我改用AssertJ'sfield-by-field comparisons
  • 开发者需要这个。感谢您的替代方案 =) 在 Rust 中,您只需在 struct 上键入 #[derive(PartialEq)],然后您可以在任何地方测试深度相等。它被大量并成功地用于测试目的。通过使用标准反射方法,Java 肯定会从中受益。
【解决方案2】:

一种方法是使用反射来比较对象——但这很棘手。另一种策略是比较序列化对象的字节数组:

class dummy implements Serializable {
    dummy2 nestedClass;
}

class dummy2  implements Serializable {
    int intVal;
}

@Test
public void testRefEqu() throws IOException {

    dummy inst1 = new dummy();
    inst1.nestedClass = new dummy2();
    inst1.nestedClass.intVal = 2;

    dummy inst2 = new dummy();
    inst2.nestedClass = new dummy2();
    inst2.nestedClass.intVal = 2;

    boolean isEqual1 = EqualsBuilder.reflectionEquals(inst1.nestedClass, inst2.nestedClass);
    boolean isEqual2 = EqualsBuilder.reflectionEquals(inst1, inst2);

    System.out.println(isEqual1);
    System.out. println(isEqual2);

    ByteArrayOutputStream baos1 =new ByteArrayOutputStream();
    ObjectOutputStream oos1 = new ObjectOutputStream(baos1);
    oos1.writeObject(inst1);
    oos1.close();

    ByteArrayOutputStream baos2 =new ByteArrayOutputStream();
    ObjectOutputStream oos2 = new ObjectOutputStream(baos2);
    oos2.writeObject(inst1);
    oos2.close();

    byte[] arr1 = baos1.toByteArray();
    byte[] arr2 = baos2.toByteArray();

    boolean isEqual3 = Arrays.equals(arr1, arr2);

    System.out.println(isEqual3);

}

您的应用程序对对象进行序列化和反序列化,因此这种方法似乎是解决您的问题的最快解决方案(就 CPU 操作而言)。

【讨论】:

  • 干杯约翰尼,我曾考虑过比较序列化形式,这是一种巧妙的方法,可以解决 EqualsBuilder 的缺点。也就是说,它不会完全验证序列化和反序列化,因为它不能完全确认原始的非序列化形式与反序列化形式相同。问候
  • 嘿霍华德,你能举个例子吗?我确信一个对象只有一种序列化形式和字节表示。
  • 嗨,约翰尼,请记住,这样做的原因是为了测试我的错误代码。如果我的序列化程序无法序列化特定字段,则比较序列化版本不会发现问题。
  • @HowardMay 我也有同样的情况。这个答案让我想起了我很久以前所做的事情。这种序列化测试方法有效,因为 java 标准序列化有效。 (或者你得到一个序列化异常,或者你覆盖了 java 序列化)。
【解决方案3】:

您可以使用 AssertJ 的field by field recursive comparison 功能,例如:

import static org.assertj.core.api.BDDAssertions.then;

then(actualObject).usingRecursiveComparison().isEqualTo(expectedObject);

【讨论】:

    【解决方案4】:

    在相关类上实现equals() 方法。每个调用的 equals 将比较嵌套类的相等性(或者,如果您愿意,将比较数据成员的相等性)。正确编写的 equals 方法总是会导致深度比较。

    在您的示例中,dummy 类等于是这样的:

    public boolean equals(Object other)
    {
        if (other == this) return true;
        if (other instanceOf dummy)
        {
            dummy dummyOther = (dummy)other;
            if (nestedClass == dummyOther.nestedClass)
            {
               return true;
            }
            else if (nestedClass != null)
            {
               return nestedClass.equals(dummyOther);
            }
            else // nestedClass == null and dummyOther.nestedClass != null.
            {
               return false;
            }
        }
        else
        {
          return false;
        }
    }
    

    【讨论】:

    • 我知道这是实现此目的的正常方式,并且在大多数情况下是推荐的方式。用于计算相等性的内置机制是健壮且可扩展的,以允许自定义类定义相等性对它们意味着什么。不幸的是,我的要求使我无法在所有嵌套类上实现 equals(),因此我希望使用反射。谢谢
    • 这种方法的问题是它不能处理循环对象图。使用这种方法比较循环对象图将导致无限递归。
    • 我同意循环对象图将是一个问题。就像序列化和故障转移一样(这可能只是序列化的一个症状)。
    【解决方案5】:

    我知道这是一个迟到的回复,但这可能对有需要的人有用。所以在我的项目中,我需要比较整个反序列化的值,看看它们是否正确匹配,我使用的 dep 是 Javers。它与 SpringBoot 集成良好,使用非常简单。

    这是示例 impl:

    @RunWith(SpringRunner.class)
    public class DeserializerTest {
    
        private HubMessage message;
        private static final String EXPECTED_INVALID_MSG = "testInvalidMessage";
        private PlatformEventHeader msgHeaders;
        private ObjectMapper objectMapper = new ObjectMapper();
        private final DeserializerClass testDeserializerClass = new DeserializerClass();
        private String TOPIC_TEST = "testTopic";
        private static final String ACTUAL_HUBMESSAGE = "XXXStringValueofmessage";
        @Before
        public void stubSetup() throws IOException {
            // setup the message
           message = new HubMessage ();
    
            //setup the headers
           msgHeaders = new PlatformEventHeader();
            ...
            ...
    
            DomainEvent event= new DomainEvent();
            Map<String, String> msgEventObjMap = new HashMap<>();
            ...
            event.setMessage(msgEventObjMap);
            message.setEvent(event);
            message.setHeader(msgHeaders);
    
        }
    
        @Test
       public void test_deserialize_validMessage() throws JsonProcessingException {
              // My actual deserialzierClass
              HubMessage messageReceived = testDeserializerClass.deserialize(TOPIC_TEST,  
                 new RecordHeaders(),ACTUAL_HUBMESSAGE .getBytes());
    
            Javers javers = JaversBuilder.javers().build();
            Diff diff = javers.compare((Object)objectMapper.convertValue(messageReceived, Object.class),
                    (Object)objectMapper.readValue(ACTUAL_HUBMESSAGE, Object.class));
            Assert.assertEquals(diff.getChanges().size() , 0);
        }
    

    我使用 Diff 来确定对象中是否存在任何更改。现在覆盖/使用 toString() 效率不高,因为它期望属性在 Matchers 中的预期值和实际值的顺序相同。 Unitils 解决了这个问题,这可能会给大量下载的第三方 jars 带来很多麻烦。所以更好的选择是使用Javers.io

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-10
      • 2016-11-18
      • 1970-01-01
      • 2012-02-13
      • 2014-07-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多