дорівнює проти Arrays.equals на Java


209

Порівнюючи масиви в Java, чи є різниці між наступними 2 твердженнями?

Object[] array1, array2;
array1.equals(array2);
Arrays.equals(array1, array2);

А якщо так, то які вони?


Погляньте також на java.util.Arrays.deepEquals (Object [] a1, Object [] a2)
ультраон

Відповіді:


299

array1.equals(array2)це те саме array1 == array2, що є, тобто це той самий масив. Як зазначає @alf, більшість людей не очікують цього.

Arrays.equals(array1, array2) порівнює вміст масивів.


Так само array.toString()може бути не дуже корисним і вам потрібно скористатися Arrays.toString(array).


59
Зауважимо, що Arrays.equals()не працює, як очікувалося, для багатовимірних масивів, він лише порівнює елементи 1-го виміру для еталонної рівності. Apache commons ArrayUtils.isEqualsпрацює з багатовимірними масивами.
Адам Паркін

4
Я приголомшений. Чи є причина для застосування array.equals для порівняння вказівників, а не для порівняння довжини та кожного об'єкта?
Озеро

2
@Lake - це порівняння довжини масиву та об'єктів, що містяться, але те, що він не робить, - це глибоке порівняння. Факт дорівнює, як робота, як очікувалося, для масивів порушена, це не повинно бути проблемою в першу чергу.
Пітер Лорі

48
@AdamParkin Ось чому ми маємо Arrays.deepEquals(Object[], Object[]).
Елліотт Фріш

3
@JeewanthaSamaraweera - це визначення для цього методу, однак .equalsвін не порівнює вміст, саме тому вам потрібен цей метод.
Пітер Лорі

86

Це сумнозвісна проблема: .equals()адже масиви сильно розбиті, просто не використовуйте їх ніколи.

Однак це не "зламано", як "хтось зробив це по-справжньому неправильно" - це просто те, що визначено, а не те, що зазвичай очікується. Тож для пуристів: це прекрасно, а це також означає, що не використовуйте його ніколи.

Тепер очікувана поведінка для equalsпорівняння даних. Типовою поведінкою є порівняння ідентичності, оскільки Objectне має жодних даних (для пуристів: так, але це не в цьому); припущення, якщо вам потрібно equalsв підкласах, ви будете його реалізовувати. У масивах для вас немає жодної реалізації, тому ви не повинні її використовувати.

Таким чином, різниця в тому, Arrays.equals(array1, array2)працює , як можна було б очікувати (тобто порівнює зміст), array1.equals(array2)повертається до Object.equalsреалізації, що , в свою чергу , порівнює ідентичність, і , таким чином , краще замінити на ==(для пуристів: так , я знаю про null).

Проблема навіть у Arrays.equals(array1, array2)тому, що елементи масиву не реалізуються equalsналежним чином. Я знаю, це дуже наївне твердження, але є дуже важливий менш очевидний випадок: розгляньте 2D масив.

2D масив на Java - це масив масивів, а масиви ' equalsпорушені (або марні, якщо ви віддаєте перевагу), тому Arrays.equals(array1, array2)не працюватимуть так, як ви очікували на 2D масивах.

Сподіваюся, що це допомагає.


13
Він не зламаний, він просто успадкований від Object.
Майкл Боргвардт

Чи має масив спеціальна реалізація для equals()? Я думав, що це не було відмінено від Object.
Martijn Courteaux

@MichaelBorgwardt - це системна бібліотека, з методом, який не робить того, що сказано в javadoc. Звуки мені досить зламані. Однак, я визнаю, що це дуже спірне твердження, але я вважаю, що "це зламано" запам'ятовується краще, і тому набагато зручніше думати про це таким чином.
alf

@MartijnCourteaux саме в цьому проблема :)
alf

3
Для масивів масивів вам потрібно Arrays.deepEquals--- це те, що someArray.equalsповинно було робитися весь час. (Споріднено:. Objects.deepEquals)
Кевін Дж. Чейз

16

Загляньте всередину реалізації двох методів, щоб глибше їх зрозуміти:

array1.equals(array2);
/**
 * Indicates whether some other object is "equal to" this one.
 * <p>
 * The {@code equals} method implements an equivalence relation
 * on non-null object references:
 * <ul>
 * <li>It is <i>reflexive</i>: for any non-null reference value
 *     {@code x}, {@code x.equals(x)} should return
 *     {@code true}.
 * <li>It is <i>symmetric</i>: for any non-null reference values
 *     {@code x} and {@code y}, {@code x.equals(y)}
 *     should return {@code true} if and only if
 *     {@code y.equals(x)} returns {@code true}.
 * <li>It is <i>transitive</i>: for any non-null reference values
 *     {@code x}, {@code y}, and {@code z}, if
 *     {@code x.equals(y)} returns {@code true} and
 *     {@code y.equals(z)} returns {@code true}, then
 *     {@code x.equals(z)} should return {@code true}.
 * <li>It is <i>consistent</i>: for any non-null reference values
 *     {@code x} and {@code y}, multiple invocations of
 *     {@code x.equals(y)} consistently return {@code true}
 *     or consistently return {@code false}, provided no
 *     information used in {@code equals} comparisons on the
 *     objects is modified.
 * <li>For any non-null reference value {@code x},
 *     {@code x.equals(null)} should return {@code false}.
 * </ul>
 * <p>
 * The {@code equals} method for class {@code Object} implements
 * the most discriminating possible equivalence relation on objects;
 * that is, for any non-null reference values {@code x} and
 * {@code y}, this method returns {@code true} if and only
 * if {@code x} and {@code y} refer to the same object
 * ({@code x == y} has the value {@code true}).
 * <p>
 * Note that it is generally necessary to override the {@code hashCode}
 * method whenever this method is overridden, so as to maintain the
 * general contract for the {@code hashCode} method, which states
 * that equal objects must have equal hash codes.
 *
 * @param   obj   the reference object with which to compare.
 * @return  {@code true} if this object is the same as the obj
 *          argument; {@code false} otherwise.
 * @see     #hashCode()
 * @see     java.util.HashMap
 */
public boolean equals(Object obj) {
    return (this == obj);
}

поки:

Arrays.equals(array1, array2);
/**
 * Returns <tt>true</tt> if the two specified arrays of Objects are
 * <i>equal</i> to one another.  The two arrays are considered equal if
 * both arrays contain the same number of elements, and all corresponding
 * pairs of elements in the two arrays are equal.  Two objects <tt>e1</tt>
 * and <tt>e2</tt> are considered <i>equal</i> if <tt>(e1==null ? e2==null
 * : e1.equals(e2))</tt>.  In other words, the two arrays are equal if
 * they contain the same elements in the same order.  Also, two array
 * references are considered equal if both are <tt>null</tt>.<p>
 *
 * @param a one array to be tested for equality
 * @param a2 the other array to be tested for equality
 * @return <tt>true</tt> if the two arrays are equal
 */
public static boolean equals(Object[] a, Object[] a2) {
    if (a==a2)
        return true;
    if (a==null || a2==null)
        return false;

    int length = a.length;
    if (a2.length != length)
        return false;

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

    return true;
}

11

Зітхнути. Ще в 70-х я був «системним програмістом» (sysadmin) для системи IBM 370, і мій роботодавець був членом групи користувачів IBM SHARE. Іноді трапляється, що хтось подав APAR (звіт про помилку) про якусь несподівану поведінку якоїсь команди CMS, і IBM відповість NOTABUG: команда робить те, що було призначено для виконання (і що говорить документація).

ПОДІЛИТИ придумали лічильник цього: BAD - Broken As Design. Я думаю, це може стосуватися цієї реалізації рівних для масивів.

З реалізацією Object.equals немає нічого поганого. Об'єкт не має членів даних, тому порівняти нема з чим. Два "Об'єкта" є рівними, якщо і тільки тоді, якщо вони насправді є одним і тим же Об'єктом (внутрішньо однакова адреса і довжина).

Але ця логіка не застосовується до масивів. Масиви мають дані, і ви очікуєте порівняння (через рівні) для порівняння даних. В ідеалі - так, як це робить Arrays.deepEquals, але принаймні так, як Arrays.equals (дрібне порівняння елементів).

Отже, проблема полягає в тому, що масив (як вбудований об'єкт) не перекриває Object.equals. String (як іменований клас) робить перевизначення Object.equals і дати результат , який ви очікуєте.

Інші наведені відповіді є правильними: [...]. Equals ([....]) просто порівнює вказівники, а не вміст. Можливо, колись хтось це виправить. А може й ні: скільки існуючих програм зламається, якщо [...]. Рівний насправді порівнював елементи? Підозрюю, не багато, але більше нуля.


5
Мені подобається абревіатура Broken.As.Замислена абревіатура
Кріс

5

Масиви успадковані equals()від Objectі , отже , порівнювати тільки повертає істину , якщо порівнювати масив проти себе.

З іншого боку, Arrays.equalsпорівнює елементи масивів.

Цей фрагмент з'ясовує різницю:

Object o1 = new Object();
Object o2 = new Object();
Object[] a1 = { o1, o2 };
Object[] a2 = { o1, o2 };
System.out.println(a1.equals(a2)); // prints false
System.out.println(Arrays.equals(a1, a2)); // prints true

Дивіться також Arrays.equals(). Інший статичний метод також може бути цікаво: Arrays.deepEquals().


1

The Arrays.equals(array1, array2):

перевірте, чи містять обидва масиви однакову кількість елементів, і всі відповідні пари елементів у двох масивах рівні.

The array1.equals(array2):

порівняйте об'єкт з іншим об'єктом та поверніть істинне, лише якщо посилання на два об'єкти рівні, як у Object.equals()


0

Архів equals()масивів успадковується Object, тому він не розглядає вміст масивів, він лише вважає кожен масив рівним собі.

Ці Arrays.equals()методи дійсно порівняти вміст масивів. Існує перевантаження для всіх примітивних типів, а одна для об'єктів використовує власні equals()методи об'єктів .


2
ви кажете "вміст масивів", це означає також багатовимірні масиви?
АланФостер

@AlanFoster: ні. Багатовимірні масиви - це масиви масивів, а це означає, що вони будуть викликати метод Arrays.equals (Object [], Object []), який називає методи sub-масивів equals ()
Michael Borgwardt

0
import java.util.Arrays;
public class ArrayDemo {
   public static void main(String[] args) {
   // initializing three object arrays
   Object[] array1 = new Object[] { 1, 123 };
   Object[] array2 = new Object[] { 1, 123, 22, 4 };
   Object[] array3 = new Object[] { 1, 123 };

   // comparing array1 and array2
   boolean retval=Arrays.equals(array1, array2);
   System.out.println("array1 and array2 equal: " + retval);
   System.out.println("array1 and array2 equal: " + array1.equals(array2));

   // comparing array1 and array3
   boolean retval2=Arrays.equals(array1, array3);
   System.out.println("array1 and array3 equal: " + retval2);
   System.out.println("array1 and array3 equal: " + array1.equals(array3));

   }
}

Ось вихід:

    array1 and array2 equal: false
    array1 and array2 equal: false

    array1 and array3 equal: true
    array1 and array3 equal: false

Бачачи подібну проблему, я особисто звертався Arrays.equals(array1, array2)за вашим запитанням, щоб уникнути плутанини.


Це здається правильним, але для масивів важливий також порядок елементів. Наприклад, якщо у вас є інший масив Object [] array4 = new Object [] {123, 1}; з Arrays.equals (array3, array4), він поверне помилковим.
jiahao
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.