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.
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
.
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());
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());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With