Deep reflective compare equals

26,302

Solution 1

From the answer to this question https://stackoverflow.com/a/1449051/116509 and from some preliminary testing, it looks like Unitils' ReflectionAssert.assertReflectionEquals does what you're expecting. (Edit: but may be abandoned, so you could try AssertJ https://assertj.github.io/doc/#assertj-core-recursive-comparison)

2021 edit: EqualsBuilder now has a testRecursive option. However the unit test libraries mentioned will give you a better failure message to help debug, so depending on context, they're still the best option.

Solution 2

One method would be to compare objects using reflection - but this is tricky. Another strategy would be to compare byte arrays of serialized objects:

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);

}

Your application serializes and deserializes objects so this approach seems to be the fastest solution (in terms of CPU operations) for your problem.

Solution 3

You can use AssertJ's field by field recursive comparison feature, for example:

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

then(actualObject).usingRecursiveComparison().isEqualTo(expectedObject);
Share:
26,302

Related videos on Youtube

Howard May
Author by

Howard May

I am a software engineer currently working in real time telecoms systems. The primary software language I use is C though I am learning C++, JavaScript, PHP and SQL. I also have an interest in Geometric problems.

Updated on September 18, 2021

Comments

  • Howard May
    Howard May over 2 years

    I am trying to validate serialize and de-serialize routines by comparing the resulting object with the original object. The routines can serialize arbitrary and deeply nested classes and consequently I want a comparison routine which can be given the original and final instance and reflectively walk through each value type and compare the values and iteratively dive into reference types to compare values.

    I have tried the Apache Commons Lang EqualsBuilder.reflectionEquals(inst1, inst2) but this does not appear to do a very deep comparison, it simply compares reference types for equality rather than diving deeper into them:

    The following code illustrates my issue. The first call to reflectionEquals returns true but the second returns false.

    Is there a library routine anyone could recommend?

    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
      DwB about 12 years
      If reflection equals is just comparing references, then it has a bug. It should do more than that.
    • Howard May
      Howard May about 12 years
      @DwB I suspect the intent of the code is to allow you to reflectively implement equals() in a specific class. This is different to what I want which is to reflect on two object instances. In this context it isn't a bug but rather a disappointment!
    • AlexWien
      AlexWien about 10 years
      I lost a half day, of this weak undocumented behavior of EqualsBuilder. If the field of an passed object is an non primitive the builde rjust calls object.equals(). Very disappointing and useless.
    • Duncan Jones
      Duncan Jones almost 10 years
      @AlexWien It's far from useless. In fact, a fully recursive equality method could be quite dangerous if not used correctly! However, I agree the documentation should be clearer. I've raised issues.apache.org/jira/browse/LANG-1034 to address the missing functionality. I'll raise another bug to address the misleading documentation.
    • maaartinus
      maaartinus almost 10 years
      @AlexWien I've never seen any method called equals doing a deep comparison. A deep comparison seems to be a hardly ever needed exception, so you shouldn't be disappointed by a standard behavior.
    • AlexWien
      AlexWien over 9 years
      @maartinus an (custom) equals methods has to check the objects for perfect equality: depending on the use case a custom equals must check all nested objects: imagine a line object with two point objects. (The line is euqal when the two points defining the line are equal = nested equal neccessary). If for some stated reasons an auto equals is dangerous, then at least it has to be documented. (Thanks Duncan)
  • Howard May
    Howard May about 12 years
    I understand this is the normal way of achieving this, and for most situations is the recommended way. The built in mechanism for calculating equality is robust and extensible to allow custom classes to define what equality means for them. Unfortunately my requirements prevent me from being able to implement equals() on all the nested classes and consequently I hope to use reflection. Thanks
  • Howard May
    Howard May about 12 years
    I share your surprise though to be fair to EqualsBuilder, and as I commented on the question, I think that its a matter of misunderstanding the intent of the routine. Perhaps this is something which could be made more clear.
  • Howard May
    Howard May about 12 years
    Cheers Johnny, I had thought about comparing the serialized form which is a neat approach which works around the shortcomings of EqualsBuilder. That said it won't fully validate the serialization and de-serialization as it doesn't quite confirm that the original unserialized form is the same as the deserialized form. Regards
  • whysoserious
    whysoserious about 12 years
    Hey Howard, could you give an example when such situation might happen? I was sure that one object has exactly one serialized form and byte representation.
  • Howard May
    Howard May about 12 years
    Hi Johnny, remember that the reason for this is to test my buggy code. If my serializer fails to serialize a particular field then comparing the serialized versions will not detect a problem.
  • Howard May
    Howard May about 12 years
    I've done some initial testing on assertReflectionEquals and it does seem to work. Thanks very much
  • jhegedus
    jhegedus about 10 years
    The problem with this approach is that it cannot handle cyclic object graphs. Comparing a cyclic object graphs using this approach would result in infinite recursion.
  • DwB
    DwB about 10 years
    I agree that cyclic object graphs will be a problem. as they are with serialization and fail over (which is probably just a symptom of serialization).
  • AlexWien
    AlexWien about 10 years
    @HowardMay I am in the same sitution. This answer remebers me to what I have done long ago. This serialisation testing approach works, because java standard serialisation works. (Or you get an Serialisation Exception, or you had overidden java serialisatoin).
  • Duncan Jones
    Duncan Jones almost 10 years
    @artbristol I've raised issues.apache.org/jira/browse/LANG-1035 to address this. I'll try to ensure it's fixed for the next release.
  • paulcm
    paulcm over 7 years
    Unitils doesn't seem to be supported any more (SO). I use AssertJ's field-by-field comparisons instead.
  • nuiun
    nuiun over 2 years
    devs need this. Thankyou for the alternatives =) In Rust you would just type #[derive(PartialEq)] on your struct and then you may test deep equality wherever you want. It is used heavily and successfully for testing purposes. Java would definitely benefit from this by using standard reflection methods too.