2

This question is related to another I posted a short while ago.

I have figured out a way to group my data the way I want. But because of how I need to group/map the objects I've already closed the stream during collection when I need to apply some filtering. I have the following code:

final Map<TeamDetails, List<Player>> teamDetailsPlayerListMap = dbRows.stream().map(row -> mapDbRowToTeamPlayerPair(row)) .collect(Collectors.groupingBy(TeamPlayerPair::getKey, Collectors.mapping(TeamPlayerPair::getValue, Collectors.toList()))); final List<Team> teams = teamDetailsPlayerListMap.entrySet().stream() .map(teamPlayerList -> mapTeamPlayerListToTeam(teamPlayerList)) .collect(Collectors.toList()); 

The problem I have is that some dbRows have an empty or null String for the playerName. I don't wan't to filter before collection as if a Team has no players (eg. only 1 db row with empty string as player name), I still want to have that in my list of Teams at the end. It will just have an empty player list.

Is there any way that I can apply a filter during collection so that empty or null strings will not be added to the list???

I have been able to achieve it with a custom collector as shown below but I'm just wondering if there is a way I can do it without a custom collector???

Function<Player, Boolean> emptyPlayerNameFilter = new Function<Player, Boolean>() { @Override public Boolean apply(Player player) { return player != null && player.getName() != null && !"".equals(player.getName()); } }; final Map<TeamDetails, List<Player>> teamDetailsPlayerListMap = dbRows.stream().map(row -> mapDbRowToTeamPlayerPair(row)) .collect(Collectors.groupingBy(TeamPlayerPair::getKey, Collectors.mapping(TeamPlayerPair::getValue, MyCollectors.toFilteredLinkedList(emptyPlayerNameFilter)))); final List<Team> finalTeams = teamDetailsPlayerListMap.entrySet().stream() .map(teamPlayerList -> mapTeamPlayerListToTeam(teamPlayerList)) .collect(Collectors.toList()); 

Where MyCollectors.toFilteredLinkedList() is:

public class MyCollectors { public static <T, A extends Collection<T>> Collector<T, ?, A> toFilteredCollection(Supplier<A> collectionFactory, Function<T, Boolean> filter) { return Collector.of( collectionFactory, (acc, entry) -> { if (filter.apply(entry)) { acc.add(entry); } }, (left, right) -> { left.addAll(right); return left; } ); } public static <T> Collector<T, ?, List<T>> toFilteredLinkedList(Function<T, Boolean> filter) { return toFilteredCollection(LinkedList<T>::new, filter); } } 

1 Answer 1

5

Seems that you need a filtering collector, which is similar to mapping, but performs a filtering. It's already implemented in Java-9, but absent in Java-8. You can put it into some utility class in your project:

public static <T, A, R> Collector<T, A, R> filtering( Predicate<? super T> filter, Collector<T, A, R> downstream) { BiConsumer<A, T> accumulator = downstream.accumulator(); Set<Characteristics> characteristics = downstream.characteristics(); return Collector.of(downstream.supplier(), (acc, t) -> { if(filter.test(t)) accumulator.accept(acc, t); }, downstream.combiner(), downstream.finisher(), characteristics.toArray(new Collector.Characteristics[0])); } 

And use like this:

final Map<TeamDetails, List<Player>> teamDetailsPlayerListMap = dbRows.stream() .map(row -> mapDbRowToTeamPlayerPair(row)) .collect(Collectors.groupingBy(TeamPlayerPair::getKey, Collectors.mapping(TeamPlayerPair::getValue, filtering(player -> player != null && player.getName() != null && !player.getName().isEmpty(), Collectors.toList())))); 

If you don't like adding filtering manually to your project, you may use some third-party library which provides such collector, for example, MoreCollectors.filtering(), which is available in my StreamEx library.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.