Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 get all elements in list

I have a List of Objects where each object that returns List<String>. How can I use Java 8 streams to get only one List<String>?

Contact class has the following method;

public List<String> getSharedFriendsIds() {
    return sharedFriendsIds;
}

And I have

List<Contact> contactsList;

What I was trying was

List<String> sharedContacts = contactsList.stream()
    .map(Contact::getSharedFriendsIds)
    .sequential()
    .collect(Collectors.toList());

But above line is not returning List<String> but rather List<List<String>> which is not what I want.

like image 652
tsingh Avatar asked Feb 19 '16 01:02

tsingh


3 Answers

You should use .flatMap() to create a single list from the sharedFriendsIds list that is contained in each Contact object from the main list contactsList. Please check;

List<String> sharedContacts = contactsList.stream()
        .map(Contact::getSharedFriendsIds)
        .filter(Objects::nonNull)
        .flatMap(Collection::stream)
        .sorted().collect(Collectors.toList());

The .filter() call is for the case when there is any Contact with sharedFriendsIds == null in the list, since that would cause NPE in the next line, we ought to filter those out. There are other ways to achieve that like;

- Optional

    List<String> sharedContacts = contactsList.stream()
            .flatMap(contacts -> Optional.ofNullable(contacts.getSharedFriendsIds())
                    .map(Collection::stream).orElseGet(Stream::empty))
            .sorted().collect(Collectors.toList());

  Where the filtering of null `sharedFriendsIds` are done in such a way that 
 they are absorbed into the `flatMap` logic as empty streams.

- emptyIfNull()

You can include collections dependency from apache.commons and use CollectionUtils.emptyIfNull method as follows;

    public static <T> Stream<T> collectionAsStream(Collection<T> collection) {
        return emptyIfNull(collection).stream();
    }

then call this from original stream like;

     List<String> sharedContacts = contactsList.stream()
             .map(Contact::getSharedFriendsIds)
             .flatMap(Foo::collectionAsStream)
             .sorted().collect(Collectors.toList());

Also you used .sequential for the sort logic, I guess, you should've used .sorted method, since sequential is for triggering non-parallel usage, which is already the default configuration of a Stream.

like image 154
buræquete Avatar answered Oct 16 '22 06:10

buræquete


There is no reason to use .sequential() here, streams are sequential by default.

List<String> sharedContacts = contactsList.stream()
        .map(Contact::getSharedFriendsIds)
        .filter(Objects::nonNull)
        .flatMap(Collection::stream)
        .collect(Collectors.toList());

In natural order;

List<String> sharedContacts = contactsList.stream()
        .map(Contact::getSharedFriendsIds)
        .filter(Objects::nonNull)
        .flatMap(Collection::stream)
        .sorted().collect(Collectors.toList());
like image 26
dmitryvinn Avatar answered Oct 16 '22 06:10

dmitryvinn


A pattern I've found very useful is to let the parent class (in this case the Contact class) create and return the streams of children objects (in this case the share friends ids):

public class Contact {

    private List<String> sharedFriendsIds;

    public Stream<String> sharedFriendsIds() {
        return sharedFriendsIds == null ? Stream.empty() : sharedFriendsIds.stream();
    }

    public List<String> getSharedFriendsIds() {
        return sharedFriendsIds;
    }
}

The convention is to name the method that returns the stream as the attribute being streamed. This method already contains the null-check.

Then, getting the shared friends ids for all contacts is much easier:

List<String> sharedContacts = contactsList.stream()
        .flatMap(Contact::sharedFriendsIds)
        .collect(Collectors.toList());

You need to use flatMap() in order to flatten the elements of the child list into a single list, otherwise you'd get a list of streams.

Note 1: you don't need to use sequential(), since using stream() on the list of contacts already returns a sequential stream.

Note 2: if you want the final list to be sorted, then you should use sorted() on the stream:

List<String> sharedContacts = contactsList.stream()
        .flatMap(Contact::sharedFriendsIds)
        .sorted().collect(Collectors.toList());
like image 2
fps Avatar answered Oct 16 '22 07:10

fps