Deep reflective compare equals

2019-01-22 14:52发布

问题:

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

回答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://joel-costigliola.github.io/assertj/assertj-core-features-highlight.html#field-by-field-recursive)

I am very alarmed by this behaviour of EqualsBuilder so thanks for the question. There are quite a few answers on this site recommending it - I wonder if the people recommending it realised it does this?



回答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.



回答3:

Implement the equals() method on the classes in question. The equals of each call will compare the equality of the nested classes (or, if you like, will compare the equality of the data members). A correctly written equals method will always result in a deep compare.

In your example, the dummy class equals would be something like this:

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