Рубрики
Без рубрики

Сравнение объектов в Java

Узнайте о сравнении объектов в Java.

Автор оригинала: François Dupire.

1. введение

Сравнение объектов является важной особенностью объектно-ориентированных языков программирования.

В этом уроке мы рассмотрим некоторые функции языка Java, которые позволяют нам сравнивать объекты. Кроме того, мы рассмотрим такие функции во внешних библиотеках.

2.

Давайте начнем с операторов == и != , которые могут определить, являются ли два объекта Java одинаковыми или нет, соответственно.

2.1. Примитивы

Для примитивных типов быть одинаковым означает иметь равные значения:

assertThat(1 == 1).isTrue();

Благодаря автоматической распаковке это также работает при сравнении примитивного значения с его аналогом типа оболочки :

Integer a = new Integer(1);
assertThat(1 == a).isTrue();

Если два целых числа имеют разные значения, оператор == вернет false , а оператор != вернет true .

2.2. Объекты

Допустим, мы хотим сравнить два типа Integer wrapper с одним и тем же значением:

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a == b).isFalse();

При сравнении двух объектов значение этих объектов не равно 1. Скорее, это их адреса памяти в стеке , которые отличаются, поскольку оба объекта были созданы с использованием оператора new . Если бы мы назначили a |//b , то у нас был бы другой результат:

Integer a = new Integer(1);
Integer b = a;

assertThat(a == b).isTrue();

Теперь давайте посмотрим, что происходит, когда мы используем Integer#значение метода factory:

Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(1);

assertThat(a == b).isTrue();

В этом случае они считаются одинаковыми. Это связано с тем, что метод valueOf() хранит Целое число в кэше, чтобы избежать создания слишком большого количества объектов-оболочек с одним и тем же значением. Поэтому метод возвращает один и тот же экземпляр Integer для обоих вызовов.

Java также делает это для String :

assertThat("Hello!" == "Hello!").isTrue();

Однако, если бы они были созданы с помощью оператора new , то они не были бы одинаковыми.

Наконец, две null ссылки считаются одинаковыми, в то время как любой объект, не являющийся null , будет считаться отличным от null :

assertThat(null == null).isTrue();

assertThat("Hello!" == null).isFalse();

Конечно, поведение операторов равенства может быть ограничивающим. Что делать, если мы хотим сравнить два объекта, сопоставленные с разными адресами, и все же считать их равными на основе их внутренних состояний? Мы увидим это в следующих разделах.

3. Метод Object#equals

Теперь давайте поговорим о более широкой концепции равенства с помощью метода equals () .

Этот метод определен в классе Object , так что каждый объект Java наследует его. По умолчанию его реализация сравнивает адреса памяти объектов, поэтому он работает так же, как оператор == //. Однако мы можем переопределить этот метод, чтобы определить, что означает равенство для наших объектов.

Во-первых, давайте посмотрим, как он ведет себя для существующих объектов, таких как Integer :

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a.equals(b)).isTrue();

Метод по-прежнему возвращает true , когда оба объекта одинаковы.

Следует отметить, что мы можем передать объект null в качестве аргумента метода, но, конечно, не в качестве объекта, на котором мы вызываем метод.

Мы можем использовать метод equals() с нашим собственным объектом. Допустим, у нас есть Человек класс:

public class Person {
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Мы можем переопределить метод equals() для этого класса, чтобы мы могли сравнить два Person s на основе их внутренних данных:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person that = (Person) o;
    return firstName.equals(that.firstName) &&
      lastName.equals(that.lastName);
}

Для получения дополнительной информации см. статья на эту тему .

4. Объекты#равны Статическому методу

Теперь давайте рассмотрим Objects#equals статический метод . Ранее мы упоминали, что мы не можем использовать null в качестве значения первого объекта, иначе возникнет исключение NullPointerException .

То равняется() метод Объекты вспомогательный класс решает эти проблемы. Он принимает два аргумента и сравнивает их, также обрабатывая нулевой ценности.

Давайте снова сравним Человека объекты с:

Person joe = new Person("Joe", "Portman");
Person joeAgain = new Person("Joe", "Portman");
Person natalie = new Person("Natalie", "Portman");

assertThat(Objects.equals(joe, joeAgain)).isTrue();
assertThat(Objects.equals(joe, natalie)).isFalse();

Как мы уже говорили, метод обрабатывает значения null . Поэтому, если оба аргумента null , он вернет true , а если только один из них null , он вернет false .

Это может быть очень удобно. Допустим, мы хотим добавить необязательную дату рождения в наш Человек класс:

public Person(String firstName, String lastName, LocalDate birthDate) {
    this(firstName, lastName);
    this.birthDate = birthDate;
}

Затем нам придется обновить наш метод equals () , но с обработкой null|/. Мы могли бы сделать это, добавив это условие в наш метод equals() :

birthDate == null ? that.birthDate == null : birthDate.equals(that.birthDate);

Однако, если мы добавим в наш класс много полей, допускающих обнуление, это может стать действительно грязным. Использование метода объекта#equals в нашей реализации equals() намного чище и улучшает читаемость:

Objects.equals(birthDate, that.birthDate);

5. Сопоставимый интерфейс

Логика сравнения также может использоваться для размещения объектов в определенном порядке. | Сопоставимый интерфейс позволяет нам определять порядок между объектами , определяя, является ли объект больше, равен или меньше другого.

Интерфейс Comparable является универсальным и имеет только один метод, compareTo() , который принимает аргумент универсального типа и возвращает int . Возвращаемое значение отрицательно, если this меньше аргумента, 0, если они равны, и положительно в противном случае.

Допустим, в нашем классе Person мы хотим сравнить объекты Person по их фамилии:

public class Person implements Comparable {
    //...

    @Override
    public int compareTo(Person o) {
        return this.lastName.compareTo(o.lastName);
    }
}

Метод compareTo() вернет отрицательный int , если вызывается с Человеком , имеющим большую фамилию, чем this , ноль, если та же фамилия, и положительный в противном случае.

Для получения дополнительной информации ознакомьтесь с нашим статья на эту тему .

6. Интерфейс компаратора

Comparator interface является универсальным и имеет метод compare , который принимает два аргумента этого универсального типа и возвращает целое число . Мы уже видели этот шаблон ранее с интерфейсом Comparable .

Компаратор аналогичен; однако он отделен от определения класса. Поэтому мы можем определить столько Компараторов , сколько нам нужно для класса, где мы можем предоставить только одну Сопоставимую реализацию.

Давайте представим, что у нас есть веб-страница, отображающая людей в виде таблицы, и мы хотим предложить пользователю возможность сортировать их по именам, а не по фамилиям. Это невозможно с Comparable , если мы также хотим сохранить нашу текущую реализацию, но мы могли бы реализовать наши собственные Компараторы .

Давайте создадим Person Компаратор , который будет сравнивать их только по именам:

Comparator compareByFirstNames = Comparator.comparing(Person::getFirstName);

Давайте теперь отсортируем Список людей, использующих этот Компаратор :

Person joe = new Person("Joe", "Portman");
Person allan = new Person("Allan", "Dale");

List people = new ArrayList<>();
people.add(joe);
people.add(allan);

people.sort(compareByFirstNames);

assertThat(people).containsExactly(allan, joe);

В интерфейсе Comparator есть другие методы, которые мы можем использовать в нашей реализации compareTo() :

@Override
public int compareTo(Person o) {
    return Comparator.comparing(Person::getLastName)
      .thenComparing(Person::getFirstName)
      .thenComparing(Person::getBirthDate, Comparator.nullsLast(Comparator.naturalOrder()))
      .compare(this, o);
}

В этом случае мы сначала сравниваем фамилии, а затем имена. Затем мы сравниваем даты рождения, но поскольку они обнуляются, мы должны сказать как с этим справиться поэтому мы даем второй аргумент, говорящий, что их следует сравнивать в соответствии с их естественным порядком, но с нулевыми значениями, идущими последними.

7. Apache Commons

Теперь давайте взглянем на библиотеку Apache Commons . Прежде всего, давайте импортируем зависимость Maven :


    org.apache.commons
    commons-lang3
    3.11

7.1. Метод ObjectUtils#NotEqual

Во-первых, давайте поговорим о методе ObjectUtils#NotEqual . Требуется два аргумента Object , чтобы определить, не равны ли они, в соответствии с их собственной реализацией метода equals () . Он также обрабатывает значения null .

Давайте повторно используем наши примеры String :

String a = new String("Hello!");
String b = new String("Hello World!");

assertThat(ObjectUtils.notEqual(a, b)).isTrue();

Следует отметить, что ObjectUtils имеет метод equals () . Однако это устарело с Java 7, когда появились Objects#equals

7.2. Метод сравнения объектов#

Теперь давайте сравним порядок объектов с методом ObjectUtils#compare . Это универсальный метод, который принимает два Сопоставимых аргумента этого универсального типа и возвращает Целое число .

Давайте еще раз посмотрим, как использовать Strings :

String first = new String("Hello!");
String second = new String("How are you?");

assertThat(ObjectUtils.compare(first, second)).isNegative();

По умолчанию метод обрабатывает значения null , считая их большими. Он предлагает перегруженную версию, которая предлагает инвертировать это поведение и считать их меньшими, принимая аргумент boolean .

8. Гуава

Теперь давайте взглянем на Гуава . Прежде всего, давайте импортируем зависимость :


    com.google.guava
    guava
    29.0-jre

8.1. Объекты#равный метод

Подобно библиотеке Apache Commons, Google предоставляет нам метод определения равенства двух объектов, Объекты#равны . Хотя они имеют разные реализации, они возвращают одни и те же результаты:

String a = new String("Hello!");
String b = new String("Hello!");

assertThat(Objects.equal(a, b)).isTrue();

Хотя он не помечен как устаревший, JavaDoc этого метода говорит, что его следует считать устаревшим, поскольку Java 7 предоставляет метод Objects#equals .

8.2. Методы сравнения

Теперь библиотека Guava не предлагает метод для сравнения двух объектов (мы увидим в следующем разделе, что мы можем сделать для этого), но она предоставляет нам методы для сравнения примитивных значений . Давайте возьмем вспомогательный класс Ints и посмотрим, как работает его метод compare() :

assertThat(Ints.compare(1, 2)).isNegative();

Как обычно, он возвращает целое число , которое может быть отрицательным, нулевым или положительным, если первый аргумент меньше, равен или больше второго, соответственно. Аналогичные методы существуют для всех примитивных типов, за исключением bytes .

8.3. Класс сравнительной цепи

Наконец, библиотека Guava предлагает класс ComparisonChain , который позволяет нам сравнивать два объекта с помощью цепочки сравнений. Мы можем легко сравнить два Человека объекта по имени и фамилии:

Person natalie = new Person("Natalie", "Portman");
Person joe = new Person("Joe", "Portman");

int comparisonResult = ComparisonChain.start()
  .compare(natalie.getLastName(), joe.getLastName())
  .compare(natalie.getFirstName(), joe.getFirstName())
  .result();

assertThat(comparisonResult).isPositive();

Базовое сравнение достигается с помощью метода compareTo () , поэтому аргументы, передаваемые методам compare () , должны быть либо примитивами, либо Сопоставимыми s.

9. Заключение

В этой статье мы рассмотрели различные способы сравнения объектов в Java. Мы рассмотрели разницу между одинаковостью, равенством и упорядоченностью. Мы также рассмотрели соответствующие функции в библиотеках Apache Commons и Guava.

Как обычно, полный код этой статьи можно найти на GitHub .