Java 8: более эффективный способ сравнения списков разных типов?
в модульном тесте я хочу проверить, что два списка содержат одни и те же элементы. Список для тестирования состоит из списка Person
объекты, где одно поле типа String
извлекается. Другой список содержит String
литералы.
для выполнения этой задачи часто можно найти следующий фрагмент кода (см. ответ):
List<Person> people = getPeopleFromDatabasePseudoMethod();
List<String> expectedValues = Arrays.asList("john", "joe", "bill");
assertTrue(people.stream().map(person -> person.getName()).collect(Collectors.toList()).containsAll(expectedValues));
The Person
класс defiend как:
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
// other getters and setters
}
в приведенном выше примере список лиц (или человек) преобразуется в список строк с использованием методов Java 8, и сравнение выполняется старомодным способом.
теперь мне интересно, есть ли более прямой или более эффективный способ сделать сравнение, используя другие операторы Java 8, например allMatch()
или какой-нибудь Predicate<T>
или что-то еще.
2 ответов
код вашего вопроса не отражает то, что вы описываете в комментариях. В комментариях вы говорите, что все имена должны присутствовать и размер должен соответствовать, другими словами, только порядок может отличаться.
код
List<Person> people = getPeopleFromDatabasePseudoMethod();
List<String> expectedValues = Arrays.asList("john", "joe", "bill");
assertTrue(people.stream().map(person -> person.getName())
.collect(Collectors.toList()).containsAll(expectedValues));
которому не хватает теста на размер people
, другими словами, позволяет дубликаты. Далее, используя containsAll
объединение двух List
s в очень неэффективно. Гораздо лучше, если вы используете тип коллекции, который отражает ваше намерение, т. е. не имеет дубликатов, не заботится о заказе и имеет эффективный поиск:
Set<String> expectedNames=new HashSet<>(expectedValues);
assertTrue(people.stream().map(Person::getName)
.collect(Collectors.toSet()).equals(expectedNames));
С помощью этого решения вам не нужно тестировать размер вручную, уже подразумевается, что наборы имеют одинаковый размер, если они совпадают, только порядок может отличаться.
существует решение, которое не требует сбора имен persons
:
Set<String> expectedNames=new HashSet<>(expectedValues);
assertTrue(people.stream().allMatch(p->expectedNames.remove(p.getName()))
&& expectedNames.isEmpty());
но он работает только если expectedNames
- это временный набор, созданный из статической коллекции expected имена. Как только вы решите заменить свою статическую коллекцию на Set
, первое решение не требует временного набора, а последнее не имеет преимущества перед ним.
Если количество элементов должно быть одинаковым, то лучше сравнить наборы:
List<Person> people = getPeopleFromDatabasePseudoMethod();
Set<String> expectedValues = new HashSet<>(Arrays.asList("john", "joe", "bill"));
assertEquals(expectedValues,
people.stream().map(Person::getName).collect(Collectors.toSet()));
на equals
метод для правильно реализованных наборов должен иметь возможность сравнивать различные типы наборов: он просто проверяет, является ли содержимое одинаковым (игнорируя порядок, конечно).
используя assertEquals
более удобна, так как в случае неудачи сообщение об ошибке будет содержать строковое представление набора.