user641887
user641887

Reputation: 1576

Java 8: Iterate and group objects from list based on object attributes

I have a list (GlobalBooks) which looks similar to the below

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {

        GlobalBooks globalBooks = new GlobalBooks();

        List<Book> bookList  = new ArrayList<Book>();


        Book book = new Book();
        List<BookContent> bookContents = new ArrayList<BookContent>();

        book.setBookName("A");
        BookContent content = new BookContent();
        content.setDescription("December 2016");
        content.setComponentID(20l);
        bookContents.add(content);

        content = new BookContent();
        content.setDescription("January 2016");
        content.setComponentID(30l);
        bookContents.add(content);

        content = new BookContent();
        content.setDescription("Febuary 2016");
        content.setComponentID(40l);
        bookContents.add(content);
        book.setContents(bookContents);

        bookList.add(book);


        book = new Book();
        bookContents = new ArrayList<BookContent>();

        book.setBookName("B");
        content = new BookContent();
        content.setDescription("December 2016");
        content.setComponentID(20l);
        bookContents.add(content);

        content = new BookContent();
        content.setDescription("January 2016");
        content.setComponentID(30l);
        bookContents.add(content);

        content = new BookContent();
        content.setDescription("Febuary 2016");
        content.setComponentID(40l);
        bookContents.add(content);
        book.setContents(bookContents);

        bookList.add(book);

        globalBooks.setBooks(bookList);


        System.out.println(globalBooks);


    }

}

I am looking for java 8 functions which could stream the globalBooks response and collect a map of List of Books based on the description field in the BooksContent.

Map<String,List<Books>> i.e. all the books with the same description should be grouped together by description ?

I can do this via regular java but the code becomes too messy and untidy.

Upvotes: 2

Views: 6092

Answers (3)

MikaelF
MikaelF

Reputation: 3615

This will do the job:

Map<String, List<String>> result = bookList.stream().flatMap(b -> b.getContents().stream())
    .collect(Collectors.toMap(str -> str, str -> bookList.stream()
    .filter(b -> b.getContents().contains(str))
    .collect(Collectors.toList()), (a, b) -> {a.addAll(b);return a;}));

If you want to execute this in parallel, simple replace the collector with Collectors.toConcurrentMap.

Upvotes: 1

Patrick Parker
Patrick Parker

Reputation: 4959

Since each book has multiple contents with different descriptions, you would need each book to appear in multiple map entries. So I would use a flatMap to create a stream element for each BookContent, specifically a Description+Book pair. Then collect into a map as usual with Collectors.groupingBy:

    Map<String,List<Book>> result = globalBooks.getBooks().stream().flatMap(
        (Book b) -> b.getContents().stream().map(
            (BookContent bc) -> new Pair<>(bc.getDescription(), b)
        )
    ).collect(Collectors.groupingBy(
        Pair::getKey,
        Collectors.mapping(Pair::getValue, Collectors.toList())
    ));

Note:

The Pair class I have used above is from javafx.util.Pair. However you could just as easily use AbstractMap.SimpleEntry, your own Pair class, or any collection data type capable of holding two Objects.

Upvotes: 6

Rajiv Kapoor
Rajiv Kapoor

Reputation: 335

Another solution could be

    Map<String,List<Book>> result = new HashMap<String, List<Book>>();

    globalBooks.getBooks().stream().forEach(book->{

        book.getContents().stream().forEach(bookContent->{

            result.computeIfAbsent(bookContent.getDescription(),(list)->new ArrayList<Book>()).add(book);
        });
    });

Upvotes: 1

Related Questions