Your's Deeply - Why Arrays.deepEquals When We Have Arrays.equals
Join the DZone community and get the full member experience.
Join For Free While everybody would naturally accept the following lines of code on
grounds of reference equality and value equality and that String and
wrappers override the equals
method, it takes some effort at first to accept the behavior of Arrays.equals
and Arrays.deepEquals
Object obj1=new Object(); Object obj2=new Object(); String hello1=new String("hello"); String hello2=new String("hello"); System.out.println(hello1.equals(hello2)); //returns true System.out.println(hello1==hello2); //returns false System.out.println(obj1.equals(obj2)); //returns false System.out.println(obj1==obj2); //returns false
Boring Stuff :-(
First, the similarities
Both Arrays.equals
and Arrays.deepEquals
are similar in certain behaviors as
- If they are the same object (reference equality), they return true
- If either of the compared objects are null, then return false
- If the array lengths are not equal, then return false
- They care about order (position)
Next, the differences
Arrays.equals is really just skin deep
Opening up the souce, we could see that the lousy Array.equals
just does this
for (int i=0; i<length; i++) { Object o1 = a[i]; Object o2 = a2[i]; if (!(o1==null ? o2==null : o1.equals(o2))) return false; }
So, it just loops through the given arrays, does a equals
on each of the pairs. This means that if you are passing in a String
/Wrapper
array or any other arrays whose equals method is overridden, then they
are equal. Non-equals-overridden classes (derived from Object
) will return false.
Arrays.deepEquals looks really deep
From the source, we could understand that Arrays.deepEquals
- Loops through the input arrays, gets each pair
- Analyses the type of each pair
- Delegates the
equal
deciding logic to one of the overloadedArrays.equals
if they are one of the primitive arrays - Delegates recursively to
Arrays.deepEquals
if it is an Object array - Calls the respective object’s
equals
, for any other objectfor (int i = 0; i < length; i++) { Object e1 = a1[i]; Object e2 = a2[i]; if (e1 == e2) continue; if (e1 == null) return false; // Figure out whether the two elements are equal boolean eq; if (e1 instanceof Object[] && e2 instanceof Object[]) eq = deepEquals ((Object[]) e1, (Object[]) e2); else if (e1 instanceof byte[] && e2 instanceof byte[]) eq = equals((byte[]) e1, (byte[]) e2); … … else eq = e1.equals(e2); if (!eq) return false; } return true;
Now, the Awesome stuff !!
Equals not overridden (nested and non-nested)
While doing an Arrays.equals for nested or non-nested ‘non-overridden equals’ objects, it is safe to assume that if Arrays.equals return false, then Arrays.deepEquals also return false.
Non-Nested
So, given two arrays
private YourClass[] equalsNotOverriddenArrayNonNested1={new YourClass(), new YourClass()}; private YourClass[] equalsNotOverriddenArrayNonNested2={new YourClass(), new YourClass()};
where YourClass
is simply
public class YourClass { }
then the following assertions are true
assertFalse(Arrays.equals(equalsNotOverriddenArrayNonNested1, equalsNotOverriddenArrayNonNested2)); assertFalse(Arrays.deepEquals(equalsNotOverriddenArrayNonNested1, equalsNotOverriddenArrayNonNested2));
Nested
Also given two arrays,
private Object[] equalsNotOverriddenArrayNested1={new YourClass(), new YourClass[]{new YourClass()}}; private Object[] equalsNotOverriddenArrayNested2={new YourClass(), new YourClass()};
the following assertions are true
assertFalse(Arrays.equals(equalsNotOverriddenArrayNested1, equalsNotOverriddenArrayNested2)); assertFalse(Arrays.deepEquals(equalsNotOverriddenArrayNested1, equalsNotOverriddenArrayNested2));
Equals overridden
Non-Nested
While doing an Arrays.equals for non-nested ‘overridden equals’ objects, it can be said that if Arrays.equals is true, then Arrays.deepEquals also return true.
Given two String arrays
assertTrue(Arrays.equals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2)); assertTrue(Arrays.deepEquals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2));
the following assertions are true
assertTrue(Arrays.equals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2)); assertTrue(Arrays.deepEquals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2));
since they are just one-to-one equals
call on each pair.
Nested
Interesting scenario : While doing an Arrays.equals for nested ‘overridden equals’ objects, if Arrays.equals is false, then Arrays.deepEquals need not be false.
Consider two Object arrays which has two values - a String and a String array
assertFalse(Arrays.equals(equalsOverriddenArrayNested1, equalsOverriddenArrayNested2)); assertTrue(Arrays.deepEquals(equalsOverriddenArrayNested1, equalsOverriddenArrayNested2));
The result for Arrays.deepEquals
is logical since each
(from the source), the method loops through each pair of elements,
checks whether it is an array type and calls deepEquals
on each of the pair. If it is an non-array type, then it just calls the equals
on the object.
However, the result of Arrays.equals
is tricky but at the same time obvious. The Arrays.equals
method blindly calls equals
on each pair and since the second arguments String[]
are of Object
type (whose equals
is not overridden), it checks for reference equality and fails !!
The entire testcase can be found below :
import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.util.Arrays; import org.testng.annotations.Test; public class DeepEquals { private Object[] equalsOverriddenArrayNonNested1={"101","201"}; private Object[] equalsOverriddenArrayNonNested2={"101","201"}; private YourClass[] equalsNotOverriddenArrayNonNested1={new YourClass(), new YourClass()}; private YourClass[] equalsNotOverriddenArrayNonNested2={new YourClass(), new YourClass()}; private Object[] equalsNotOverriddenArrayNested1={new YourClass(), new YourClass[]{new YourClass()}}; private Object[] equalsNotOverriddenArrayNested2={new YourClass(), new YourClass()}; private Object[] equalsOverriddenArrayNested1={new String("hello"), new String[]{new String("hello")}}; private Object[] equalsOverriddenArrayNested2={new String("hello"), new String[]{new String("hello")}}; @Test public void stringArrayTest(){ assertFalse (equalsOverriddenArrayNonNested1==equalsOverriddenArrayNonNested2); assertFalse(equalsOverriddenArrayNonNested1.equals(equalsOverriddenArrayNonNested2)); assertTrue(Arrays.equals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2)); assertTrue(Arrays.deepEquals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2)); } @Test public void objectArrayTestNonNested(){ assertFalse (equalsNotOverriddenArrayNonNested1==equalsNotOverriddenArrayNonNested2); assertFalse(equalsNotOverriddenArrayNonNested1.equals(equalsNotOverriddenArrayNonNested2)); assertFalse(Arrays.equals(equalsNotOverriddenArrayNonNested1, equalsNotOverriddenArrayNonNested2)); assertFalse(Arrays.deepEquals(equalsNotOverriddenArrayNonNested1, equalsNotOverriddenArrayNonNested2)); } @Test public void objectArrayTestNested(){ assertFalse (equalsNotOverriddenArrayNested1==equalsNotOverriddenArrayNested2); assertFalse(equalsNotOverriddenArrayNested1.equals(equalsNotOverriddenArrayNested2)); assertFalse(Arrays.equals(equalsNotOverriddenArrayNested1, equalsNotOverriddenArrayNested2)); assertFalse(Arrays.deepEquals(equalsNotOverriddenArrayNested1, equalsNotOverriddenArrayNested2)); } @Test public void objectArrayTest2(){ assertFalse (equalsOverriddenArrayNested1==equalsOverriddenArrayNested2); assertFalse(equalsOverriddenArrayNested1.equals(equalsOverriddenArrayNested2)); assertFalse(Arrays.equals(equalsOverriddenArrayNested1, equalsOverriddenArrayNested2)); assertTrue(Arrays.deepEquals(equalsOverriddenArrayNested1, equalsOverriddenArrayNested2)); } }
Published at DZone with permission of Arun Manivannan, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Tech Hiring: Trends, Predictions, and Strategies for Success
-
Deploying Smart Contract on Ethereum Blockchain
-
Strategies for Reducing Total Cost of Ownership (TCO) For Integration Solutions
-
How To Use Git Cherry-Pick to Apply Selected Commits
Comments