Deep reflective compare equals
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);
Related videos on Youtube
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, 2021Comments
-
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 about 12 yearsIf reflection equals is just comparing references, then it has a bug. It should do more than that.
-
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 about 10 yearsI 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 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 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 almost 10 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 about 12 yearsI 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 about 12 yearsI 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 about 12 yearsCheers 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 about 12 yearsHey 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 about 12 yearsHi 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 about 12 yearsI've done some initial testing on assertReflectionEquals and it does seem to work. Thanks very much
-
jhegedus over 10 yearsThe 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 over 10 yearsI 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 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 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 over 7 yearsUnitils doesn't seem to be supported any more (SO). I use AssertJ's field-by-field comparisons instead.
-
nuiun over 2 yearsdevs need this. Thankyou for the alternatives =) In Rust you would just type
#[derive(PartialEq)]
on yourstruct
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.