Статьи по Java

Поиск и удаление не “стандартных” объектов в списках Java

В Java Collections Framework коллекции типа “список” (все реализации интерфейса java.util.List) содержат методы, предназначенные для поиска и удаления объектов. В этой статье мы рассмотрим использование этих методов, в частности, применительно к ситуации, когда список содержит объекты из нестандартных классов, то есть написанных с нуля.

Предисловие

Все списки, которые происходят от java.util.List, имеют следующие методы:

  • Mетоды поиска:
    • boolean contains(Object o)
    • boolean containsAll(Collection<?> c)
    • int indexOf(Object o)
    • int lastIndexOf(Object o)
  • Методы удаления:
    • boolean remove(Object o)
    • boolean removeAll(Collection<?> c)

Кроме того, списки, также содержит метод equals(Object), который используется для проверки того, являются ли два списка эквивалентными “по смыслу”, т.е. имеют одинаковое содержание. Все только что упомянутые методы основаны на концепции “равенства” объектов, определяемой методом equals класса java.lang.Object.

Когда вы просите список найти или удалить определенный объект, список сначала сканирует свои элементы и с использованием метода equals проверяет, является ли элемент запрашиваемым объектом. Все это имеет важное значение: если список содержит объекты “стандартных” классов (например, java.lang.String, java.lang.Integer, java.util.Date, java.time.LocalDate и т.д…), то не возникает особых проблем. В то время как при вставке объектов собственных классов, написанных с нуля, нужно быть очень внимательным.

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

public class Person { private String name; private String lastname; public Person(String name, String lastname) { this.name= name; this.lastname= lastname; } // ... методы getter/setter опущены для простоты }
Code language: JavaScript (javascript)

Класс Person, как показано выше, написан по минимуму, но при этом является достаточным (с точки зрения дизайна). Обратите внимание, что методы getter/setter были опущены как для простоты и краткости, так и потому, что они не имеют значения, поскольку их наличие или отсутствие, по сути, не имеет влияния на концепцию, описанную в этой статье.

Теперь вы можете протестировать класс Person с помощью следующего кода:

import java.util.ArrayList; public class ListOfPersons { public static void main(String[] args) { ArrayList<Person> lista1 = new ArrayList<Person>(); lista1.add(new Person("Иван", "Иванов")); lista1.add(new Person("Петр", "Петров")); lista1.add(new Person("Александр", "Александров")); ArrayList<Person> lista2 = new ArrayList<Person>(); lista2.add(new Person("Иван", "Иванов")); lista2.add(new Person("Петр", "Петров")); lista2.add(new Person("Александр", "Александров")); Person gv = new Person("Александр", "Александров"); System.out.println(lista1.equals(lista2)); System.out.println(lista1.contains(gv)); System.out.println(lista1.indexOf(gv)); System.out.println(lista1.remove(gv)); } }
Code language: JavaScript (javascript)

Запустив этот код, вы получите следующий результат:

false false -1 false
Code language: JavaScript (javascript)

Этот результат говорит о следующих проблемах:

  • два списка не являются “одинаковыми” (но, как вы видите, они имеют одинаковое содержание!)
  • человек “Александр Александров” не был найден и не был удален (однако “Александр Александров” есть в списке!).

Все это происходит потому, что в классе Person метод equals не был переопределен должным образом, поэтому остался только тот, который “унаследован” от класса java.lang.Object, который основан исключительно на “идентичности” объектов. Метод equals объекта Object применяет простое сравнение оператором ==, поэтому на практике он только проверяет, имеют ли две ссылки одинаковое значение, т.е. ссылаются ли они на один и тот же объект.

Как вы можете видеть, в тестовом коде все объекты Person являются отдельными, созданными с нуля с помощью оператора new. Объект Person “Иван Иванов” из списка2 является отличным объектом от Person “Иван Иванов” из списка1. Аналогично, объект Person “Александр Александров”, используемый для поиска/удаления (тот, который присвоен переменной gv), является отдельным объектом от объектов Person “Александр Александров”, содержащихся в списках list1 и list2.

При таком сценарии и наличии в объектах Person только Object equals, сравнение с равными двумя объектами Person приведет к тому, что они окажутся “разными” объектами, даже если у нас есть четкое свидетельство того, что они имеют одинаковое содержание.

Решение

Очевидно, что решение этой проблемы заключается в соответствующем переопределении метода equals в классе Person. В целом, цель равенства – определить, имеют ли два объекта эквивалентное “значение” (содержание). Поэтому равным Person придется просто проверять, имеют ли два объекта Persona одинаковое содержимое, переходя к более точному сравнению имени и фамилии двух объектов.

public class Person { // ... такой же, как и раньше. ... @Override public boolean equals(Object obj) { if (!(obj instanceof Person)) { return false; } Person other = (Person) obj; if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } if (lastname == null) { if (other.lastname != null) { return false; } } else if (!lastname.equals(other.lastname)) { return false; } return true; } }
Code language: PHP (php)

Реализация equals может показаться немного длинной для работы только с двумя атрибутами, но это основной/стандартный способ сделать это. На самом деле к equals можно применить ряд оптимизаций, а также для сокращения кода сравнения можно использовать различные техники, например, в Java 7 можно воспользоваться статическим методом equals (Object, Object) нового класса java.util.Objects или воспользоваться классом StringUtils библиотеки Apache Commons Lang.

Однако оптимизация не относится к данной статье. Если после добавления equals в класс Person (и его перекомпиляции) вы повторно запустите тестовую программу, то получите следующий результат:

true true 2 true
Code language: JavaScript (javascript)

В итоге получаем следующий результат:

  • оба списка “равны”
  • объект Person “Александр Александров” который находится в списке (contains), был найден по индексу 2 (indexOf) и был успешно удален (remove)

Заключение

Вывод этой статьи очень прост: когда вы хотите использовать метод поиска/удаления объекта в списке или проверить, являются ли два списка “равными”, необходимо, чтобы в классе обрабатываемых объектов был соответствующим образом переопределен метод equals.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *