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

Java 8: More efficient way of comparing lists of different types?

在单元测试中,我想验证两个列表是否包含相同的元素。要测试的列表是 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));

Personclass被定义为:

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

如果元素个数必须相同,那么比较集合比较好:

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

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

您的密码是

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 替换您的静态集合,第一个解决方案不需要临时集合,而后者没有优势。

这就是我解决 2 个 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");}   

希望对有需要的人有所帮助