Search code examples
javajava-8setjava-streamcomparator

How to compare two Sets of objects Not overriding equals/hashCode with Comparator and Streams


I have two Sets like the following:

Set<Book> s1 = createBooks();
Set<Book> s2 = createBooks(); // or any set of values

and this method:

private Set<Book> createBooks() {

    List<String> name = Arrays.asList("book 1", "book 2", "book 3");
    List<BookType> type = Arrays.asList(BookType.TYPEONE, BookType.TYPETWO);

    Set<Book> listOfBooks = new HashSet<>();

    for(int i=0; i<2; i++) {
        Book book = new Book();
        book.setName(name.get(i));
        book.setType(type.get(i));
        listOfBooks.add(books);
    }

    return listOfBooks;
}

And this class as well:

Class Book {

 private String name;
 private BookType type;

 //here is getter and setter ...

 }

I did implement this Comparator, but I'm not sure if it's working properly:

   Comparator<Book> comparingBooks = (o1, o2) -> {
        if (o1.getName().compareTo(o2.getName()) == 0) {
            return o1.getType().compareTo(o2.getType());
        }
        return o1.getName().compareTo(o2.getName());
    };


     //I did try this implementation but am not sure it's working as well:

     //Comparator<Book> comparingBooks = Comparator.comparing((Book b)->b.getNName())
              .thenComparing(b->b.getType());

An example of the sets when the expected result is true:

s1 = (new Book("book 1", BookType.ONE), new Book("book 2", BookType.TWO));
s2 = (new Book("book 1", BookType.ONE), new Book("book 2", BookType.TWO));

An example of the sets when the expected result is false:

s1 = (new Book("book 1", BookType.ONE), new Book("book 2", BookType.TWO));     
s2 = (new Book("book 1", BookType.ONE), new Book("book 2", BookType.ONE));

I need to compare these sets with something like this:

// this flag should validate any difference of Name 
// and Type by returning true, 
// if both Sets have exactly the same objects, 
// it should return false.
boolean compareBooks = s1.stream().anyMatch(a -> s2.stream().anyMatch(b -> comparingBooks.compare(a, b) != 0));

I did try this as well but didn't work and always return same value:

    boolean compareBooks = SetUtils.isEqualSet(s1, s2);

I have no equals and hashcode implementation and can't implement that.

Is there any other way with java 8 and comparator to check and compare these sets?


Solution

  • A Fix for Comparator

    In order to create a Comparator in this case you need to provide generic type information explicitly, like that: <Book, String>comparing(), where Book and String are the parameter type and return type of the Function that Comparator.comparing() expects as a parameter.

    Without an explicit declaration, the compiler has not enough data to determine the type of the variable book, and inside both comparing() and thenComparing() its type will be inferred as Object.

    Comparator<Book> comparingBooks = 
          Comparator.<Book, String>comparing(book -> book.getName())
                               .thenComparing(book -> book.getType());
    

    If the case if only one of the static methods was used, the type of the book variable will be correctly inferred by the compiler as Book based on the type of the locale variable Comparator<Book> comparingBooks.

    Both method lambda expressions could be replaced with method references:

    Comparator<Book> comparingBooks =
          Comparator.<Book, String>comparing(Book::getName)
                              .thenComparing(Book::getType);
    

    for information on the syntax of generic methods, take a look at this tutorial

    for more information on how to build comparators with Java 8 methods take a look at this tutorial

    Stream

    should validate any difference of Name and Type by returning true, if both Sets have exactly the same objects, it should return false.

    Stream s1.stream().anyMatch(a -> s2.stream().anyMatch(b -> ...)) is currently checking whether there's an element in the first set s1 that is different from ANY of the elements in s2. I.e. the nested anyMatch() will return true for the first element encountered in s1 that has not matching element in s2. After the comparison of Book("book 1", BookType.ONE) (s1) with Book("book 2", BookType.TWO) (s2) anyMatch() terminates by returning true. And enclosing anyMatch() operation propagates this result.

    Precisely the same will happen with the second example. The same pair of elements differ, although you've changed a type, names are not equal. Result is true.

    Note:

    • that by convention classes are usually named with singular nouns, i.e. Book (not Books), Event, Person, etc.
    • as a rule of thumb, if your objects are intended to be used with collection they must implement equals/hashCode contract, your requirement is very unusual.