Java 8:比较不同类型列表的更有效方法吗?

时间:2015-05-19 16:04:29

标签: lambda comparison java-8 java-stream

在单元测试中,我想验证两个列表是否包含相同的元素。要测试的列表是Person对象列表的构建,其中提取了一个类型为String的字段。另一个列表包含String文字。

通常会找到以下代码片段来完成此任务(请参阅this answer):

List<Person> people = getPeopleFromDatabasePseudoMethod();
List<String> expectedValues = Arrays.asList("john", "joe", "bill");

assertTrue(people.stream().map(person -> person.getName()).collect(Collectors.toList()).containsAll(expectedValues));

Person类定义为:

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>或其他。

3 个答案:

答案 0 :(得分:18)

您的问题代码未反映您在评论中描述的内容。在评论中,您说所有名称都应该存在且大小应该匹配,换句话说,只有订单可能不同。

您的代码是

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非常低效。如果您使用反映您意图的集合类型,即没有重复项,不关心订单并且有效查找,那就更好了:

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是从预期名称的静态集合创建的临时集时,它才有效。一旦您决定用Set替换静态集合,第一个解决方案不需要临时集合,后者没有优势。

答案 1 :(得分:4)

如果元素的数量必须相同,那么比较集合会更好:

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更方便,因为如果失败,错误消息将包含您的集合的字符串表示。

答案 2 :(得分:0)

这就是我解决了两个Person列表相等的方法

public class Person {
  private String name;
  //constructor, setters & getters 
  @Override
  public boolean equals(Object o) {
    if(this == o) return true;
    if(o == null || getClass() != o.getClass()) return false
    Person p = (Person) o;
    return name.equals(p.name);
  }
  @Override
  public int hashCode() { return Objects.hash(name);}
}

// call this boolean method where (in a service for instance) you want to test the equality of 2 lists of Persons.
public boolean isListsEqual(List<Person> givenList, List<Person> dbList) {
  if(givenList == null && dbList == null) return true;
  if(!givenList.containsAll(dbList) || givenList.size() != dbList.size()) return false;
  return true;
}

// You can test it in a main method or where needed after initializing 2 lists    
boolean equalLists = this.isListsEqual(givenList, dbList);
if(equalLists) { System.out.println("Equal"); }
else {System.out.println("Not Equal");}   

我希望这可以帮助需要帮助的人。