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

前端 未结 3 1153
情深已故
情深已故 2021-02-05 16:31

In a unit test, I want to verify that two lists contain the same elements. The list to test is build of a list of Person objects, where one field of type Stri

相关标签:
3条回答
  • 2021-02-05 16:52

    If the number of elements must be the same, then it would be better to compare sets:

    List<Person> people = getPeopleFromDatabasePseudoMethod();
    Set<String> expectedValues = new HashSet<>(Arrays.asList("john", "joe", "bill"));
    assertEquals(expectedValues, 
        people.stream().map(Person::getName).collect(Collectors.toSet()));
    

    The equals method for properly implemented sets should be able to compare different types of sets: it just checks whether the contents is the same (ignoring the order of course).

    Using assertEquals is more convenient as in case of failure an error message will contain the string representation of your set.

    0 讨论(0)
  • 2021-02-05 16:58

    This is how I solved the equality of 2 lists of 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");}   
    

    I hope this can help someone in need.

    0 讨论(0)
  • 2021-02-05 17:01

    Your question’s code does not reflect what you describe in the comments. In the comments you say that all names should be present and the size should match, in other words, only the order may be different.

    Your code is

    List<Person> people = getPeopleFromDatabasePseudoMethod();
    List<String> expectedValues = Arrays.asList("john", "joe", "bill");
    
    assertTrue(people.stream().map(person -> person.getName())
                     .collect(Collectors.toList()).containsAll(expectedValues));
    

    which lacks a test for the size of people, in other words allows duplicates. Further, using containsAll combining two Lists in very inefficient. It’s much better if you use a collection type which reflects you intention, i.e. has no duplicates, does not care about an order and has an efficient lookup:

    Set<String> expectedNames=new HashSet<>(expectedValues);
    assertTrue(people.stream().map(Person::getName)
                     .collect(Collectors.toSet()).equals(expectedNames));
    

    with this solution you don’t need to test for the size manually, it is already implied that the sets have the same size if they match, only the order may be different.

    There is a solution which does not require collecting the names of persons:

    Set<String> expectedNames=new HashSet<>(expectedValues);
    assertTrue(people.stream().allMatch(p->expectedNames.remove(p.getName()))
               && expectedNames.isEmpty());
    

    but it only works if expectedNames is a temporary set created out of the static collection of expected names. As soon as you decide to replace your static collection by a Set, the first solution doesn’t require a temporary set and the latter has no advantage over it.

    0 讨论(0)
提交回复
热议问题