java 8 stream filter list of object lists

Viewed 1199

I want to filter by the object field

List<Book> = Arrays.asList<book1, book2, book3>;

class Book {
    String author;
    List<Language>;
}

class Language {
    String locale;
    int code;
}

Book book1 = new Book("author1", Arrays.asList(new Language("en", 1), new Language("fr", 3)));
Book book2 = new Book("author2", Arrays.asList(new Language("ca", 2), new Language("fr", 3)));
Book book3 = new Book("author3", Arrays.asList(new Language("en", 1)));

I want to have a list of the books which not contain Language en.

The result should be :

List<Book> = Arrays.asList<book1, book2>;

Book book1 = new Book("author1", Arrays.asList(new Language("fr", 3)));
Book book2 = new Book("author2", Arrays.asList(new Language("ca", 2), new Language("fr", 3)));

I tried to do this : but it not remove 'en' from book1

return books
        .stream()
        .filter(book -> book.getLanguages()
                .stream()
                .allMatch(value -> !"en".equals(value.getLanguage())))
        .collect(Collectors.toList());
2 Answers

First you can use filter to check any Language object has value of en and then use findFirst to get the matching Language object

return books.stream()
            .filter(book -> !book.getLanguages()
                  .stream()
                  .filter(value -> "en".equals(value.getLanguage()))
                  .findFirst().isPresent())
    .collect(Collectors.toList());

Your question is a bit confusing, but as I understand from your example, there are 2 requirements:

  1. Filter out the "en" language from each book
  2. Filter out the books having only "en" language

You can define a helper method like this:

private Book removeLanguage(Book book, String locale) {
    List<Language> languages = book.getLanguages()
                                   .stream() 
                                   .filter(l -> !l.getLocale().equals(locale))
                                   .collect(Collectors.toList());
    
    // if all languages have been removed
    if(languages.isEmpty()) return null;
    // if no languages have been removed
    if(languages.size() == book.getLanguages().size()) return book;
    // return new book with filtered list of languages
    return new Book(book.getAuthor(), languages);
}

and then use it as follows:

public List<Book> removeLanguage(List<Book> books, String locale) {
    return books.stream()
                .map(b -> removeLanguage(b, locale))
                .map(Objects::notNull)
                .collect(Collectors.toList());
}

// ...

List<Book> nonEnglishBooks = removeLanguage(books, "en");

You can indeed combine everything into a single chained pipeline. It will have a bit of object creation overhead, but still perform well on small amounts of data:

String localeToRemove = "en";

List<Book> nonEnglishBooks = 
    books.stream()
         .map(b -> new Book(b.getAuthor(), 
                            b.getLanguages()
                             .stream() 
                             .filter(l -> !l.getLocale().equals(localeToRemove))
                             .collect(Collectors.toList())
         .filter(b -> !b.getLanguages().isEmpty())
         .collect(Collectors.toList());
Related