Перейти к основному содержимому

Поиск различий между двумя списками в Java

· 3 мин. чтения

1. Обзор

Поиск различий между наборами объектов одного и того же типа данных — обычная задача программирования. В качестве примера представьте, что у нас есть список студентов, подавших заявку на экзамен, и еще один список студентов, которые его сдали. Разница между этими двумя списками даст нам студентов, не сдавших экзамен.

В Java нет явного способа поиска различий между двумя списками в List API, хотя есть несколько вспомогательных методов, которые близки.

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

2. Настройка теста

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

public class FindDifferencesBetweenListsUnitTest {

private static final List listOne = Arrays.asList("Jack", "Tom", "Sam", "John", "James", "Jack");
private static final List listTwo = Arrays.asList("Jack", "Daniel", "Sam", "Alan", "James", "George");

}

3. Использование API списка Java

Мы можем создать копию одного списка, а затем удалить все элементы, общие с другим , с помощью метода List removeAll() :

List<String> differences = new ArrayList<>(listOne);
differences.removeAll(listTwo);
assertEquals(2, differences.size());
assertThat(differences).containsExactly("Tom", "John");

Давайте перевернем это, чтобы найти различия наоборот:

List<String> differences = new ArrayList<>(listTwo);
differences.removeAll(listOne);
assertEquals(3, differences.size());
assertThat(differences).containsExactly("Daniel", "Alan", "George");

Мы также должны отметить, что если мы хотим найти общие элементы между двумя списками, List также содержит метод continueAll .

4. Использование потокового API

Java Stream можно использовать для выполнения последовательных операций с данными из коллекций, включая фильтрацию различий между списками :

List<String> differences = listOne.stream()
.filter(element -> !listTwo.contains(element))
.collect(Collectors.toList());
assertEquals(2, differences.size());
assertThat(differences).containsExactly("Tom", "John");

Как и в нашем первом примере, мы можем изменить порядок списков, чтобы найти различные элементы из второго списка:

List<String> differences = listTwo.stream()
.filter(element -> !listOne.contains(element))
.collect(Collectors.toList());
assertEquals(3, differences.size());
assertThat(differences).containsExactly("Daniel", "Alan", "George");

Следует отметить, что повторный вызов List . contains() может быть дорогостоящей операцией для больших списков.

5. Использование сторонних библиотек

5.1. Использование Google Гуавы

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

List<String> differences = new ArrayList<>(Sets.difference(Sets.newHashSet(listOne), Sets.newHashSet(listTwo)));
assertEquals(2, differences.size());
assertThat(differences).containsExactlyInAnyOrder("Tom", "John");

Следует отметить, что преобразование списка в набор будет иметь эффект дедупликации и переупорядочивания.

5.2. Использование коллекций Apache Commons

Класс CollectionUtils из Apache Commons Collections содержит метод removeAll .

Этот метод делает то же самое, что и List . removeAll , а также создайте новую коллекцию для результата :

List<String> differences = new ArrayList<>((CollectionUtils.removeAll(listOne, listTwo)));
assertEquals(2, differences.size());
assertThat(differences).containsExactly("Tom", "John");

6. Обработка повторяющихся значений

Давайте теперь посмотрим, как найти различия, когда два списка содержат повторяющиеся значения.

Для этого нам нужно удалить повторяющиеся элементы из первого списка ровно столько раз, сколько их содержится во втором списке.

В нашем примере значение «Джек» появляется дважды в первом списке и только один раз во втором списке:

List<String> differences = new ArrayList<>(listOne);
listTwo.forEach(differences::remove);
assertThat(differences).containsExactly("Tom", "John", "Jack");

Мы также можем добиться этого, используя метод вычитания из коллекций Apache Commons :

List<String> differences = new ArrayList<>(CollectionUtils.subtract(listOne, listTwo));
assertEquals(3, differences.size());
assertThat(differences).containsExactly("Tom", "John", "Jack");

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

В этой статье мы рассмотрели несколько способов поиска различий между списками .

В примерах мы рассмотрели базовое решение Java , решение, использующее Streams API, и сторонние библиотеки, такие как Google Guava и Apache Commons Collections.

Мы также увидели, как обрабатывать повторяющиеся значения.

Как всегда, полный исходный код доступен на GitHub .