Basically, I’d like to use a Predicate
to filter a generic (which extends Collection), and then return an instance of the same generic Collection implementation (preferably a new instance) e.g. implement the method signature F removeNulls(F inputs)
.
I have the following examples, but there are caveats to each (removeNulls4
is probably the closest to what I’m trying to achieve):
Caveats
removeNulls1:
- the returned list may(/will) not be an instance of
F
(requires casting)
removeNulls2:
- the implementation of
F
may not have an empty constructor - use of the
forReturn
object is not thread safe (if run in parallel)
removeNulls3:
- modifies the original list
- is reinventing the wheel/not parallelisable (would prefer to use the
filter
method) Iterator.remove()
can throw anUnsupportedOperationException
removeNulls4:
- modifies the original list
import java.util.*; import java.util.function.*; import java.util.stream.*; public class QuickTest<I, F extends Collection<I>> { Predicate<I> removeNullsPredicate = x -> x != null; @SuppressWarnings("unchecked") public F removeNulls1(F inputs) throws Exception { return (F) inputs.stream().filter(removeNullsPredicate) .collect(Collectors.toList()); } @SuppressWarnings("unchecked") public F removeNulls2(F inputs) throws Exception { F forReturn = (F) inputs.getClass().newInstance(); inputs.stream().filter(removeNullsPredicate) .collect(Collectors.toCollection(() -> forReturn)); return forReturn; } public F removeNulls3(F inputs) throws Exception { Iterator<I> iter = inputs.iterator(); while (iter.hasNext()){ I next = iter.next(); boolean test = removeNullsPredicate.test(next); if (!test){ iter.remove(); } } return inputs; } public F removeNulls4(F inputs) throws Exception { List<I> forRemoval = inputs.stream().filter(removeNullsPredicate.negate()) .collect(Collectors.toList()); inputs.removeAll(forRemoval); return inputs; } }
Answer
You could provide a Supplier<F>
as an argument:
public F removeNulls(F inputs, Supplier<F> factory) { return inputs.stream().filter(removeNullsPredicate) .collect(Collectors.toCollection(factory)); }
Then simply call:
List<I> nonNulls = removeNulls(inputs, ArrayList::new);