Статьи

Вы глубоко — почему Arrays.deepEquals, когда у нас есть Arrays.equals

 Хотя каждый, естественно, согласился бы со следующими строками кода на основании ссылочного равенства и равенства значений и того, что String и обертки переопределяют equalsметод, сначала требуется некоторое усилие, чтобы принять поведение Arrays.equalsи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

Скучный материал ?

Во-первых, сходство

И то, Arrays.equalsи другое Arrays.deepEqualsпохоже на определенные виды поведения, как

  1. Если это один и тот же объект (ссылочное равенство), они возвращают true
  2. Если какой-либо из сравниваемых объектов имеет значение null, вернуть false
  3. Если длины массива не равны, вернуть false
  4. Они заботятся о порядке (позиции)

Далее различия

Arrays.equals действительно просто кожа глубоко

Открыв соус, мы увидели, что паршивец Array.equalsделает это

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

Таким образом, он просто перебирает заданные массивы, делает a equalsна каждой из пар. Это означает, что если вы передаете Stringмассив / Wrapper или любые другие массивы, у которых метод equals переопределен, то они равны. Переопределенные не равные классы (производные от Object) вернут false.

Arrays.deepEquals выглядит действительно глубоко

Из источника мы могли понять, что Arrays.deepEquals

  1. Перебирает входные массивы, получает каждую пару
  2. Анализирует тип каждой пары
  3. Делегирует equalрешающую логику одному из перегруженных, Arrays.equalsесли они являются одним из примитивных массивов.
  4. Рекурсивно делегирует, Arrays.deepEqualsесли это массив объектов
  5. Вызывает соответствующий объект equalsдля любого другого объекта
    for (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;
    

Теперь классные вещи!

Равен не переопределен (вложенный и не вложенный)

Выполняя Arrays.equals для вложенных или не вложенных объектов «не переопределенные равно», можно с уверенностью предположить, что если Arrays.equals возвращает false, то Arrays.deepEquals также возвращает false.

Невложенных

Итак, с учетом двух массивов

private YourClass[] equalsNotOverriddenArrayNonNested1={new YourClass(), new YourClass()};
private YourClass[] equalsNotOverriddenArrayNonNested2={new YourClass(), new YourClass()};

где YourClassпросто

public class YourClass {
}

тогда верны следующие утверждения

assertFalse(Arrays.equals(equalsNotOverriddenArrayNonNested1, equalsNotOverriddenArrayNonNested2));
assertFalse(Arrays.deepEquals(equalsNotOverriddenArrayNonNested1, equalsNotOverriddenArrayNonNested2));

Уплотненный

Также дано два массива,

private Object[] equalsNotOverriddenArrayNested1={new YourClass(), new YourClass[]{new YourClass()}};
private Object[] equalsNotOverriddenArrayNested2={new YourClass(), new YourClass()};

следующие утверждения верны

 assertFalse(Arrays.equals(equalsNotOverriddenArrayNested1, equalsNotOverriddenArrayNested2));
  assertFalse(Arrays.deepEquals(equalsNotOverriddenArrayNested1, equalsNotOverriddenArrayNested2));
  

Равно переопределено

Невложенных

Выполняя Arrays.equals для не вложенных объектов «переопределенных равных», можно сказать, что если Arrays.equals имеет значение true, то Arrays.deepEquals также возвращает значение true.

Дано два строковых массива

assertTrue(Arrays.equals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2));
assertTrue(Arrays.deepEquals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2));

следующие утверждения верны

assertTrue(Arrays.equals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2));
assertTrue(Arrays.deepEquals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2));

так как они просто один-на-один equalsв каждой паре.

Уплотненный

Интересный сценарий: при выполнении Arrays.equals для вложенных «переопределенных равных» объектов, если Arrays.equals имеет значение false, то Arrays.deepEquals не должен быть ложным.

Рассмотрим два массива Object, которые имеют два значения — массив String и String

assertFalse(Arrays.equals(equalsOverriddenArrayNested1, equalsOverriddenArrayNested2));
assertTrue(Arrays.deepEquals(equalsOverriddenArrayNested1, equalsOverriddenArrayNested2));

Результат для Arrays.deepEqualsлогичен, поскольку каждый (из источника) метод проходит по каждой паре элементов, проверяет, является ли он типом массива, и вызывает deepEqualsкаждую из этих пар. Если это не тип массива, то он просто вызывает equalsобъект on.

Однако результат Arrays.equalsсложен, но в то же время очевиден. Arrays.equalsМетод вслепую вызывает equalsна каждой паре , и поскольку вторые аргументы String[]имеют Objectтип (которого equalsне переопределяется), он проверяет равенство ссылок и не !!

Весь тестовый пример можно найти ниже:

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