Search code examples
javajava-stream

Stream map on filter


When you have a Stream of Objects you can filter on them pretty elegantly.

swimmingAnimalStream = animalStream
    .filter(Animal::canSwim);

When you have slightly more complex filters instead of using Method references you have to use Lambdas.

greenAnimals = animalStream
    .filter(animal -> animal.getColor().equals(Colors.GREEN));

Is there a way to map the value before filtering on it, but still have the complete object after the filter? So the fallowing is not what I want:

animalStream
    .map(Animal::getColor)
    .filter(Colors.GREEN::equals)

With this I would be left with color information only. What I also would like to avoid is extracting the method. I am looking for a more streamlined way of doing this. Something like this for example:

animalStream
    .filter(Colors.GREEN::equals, Animal::getColor);

The method signature of this filter method would look like this.

<MAPPED> Stream<T> filter(Predicate<MAPPED> filter, Function<? super T, MAPPED> mappingFunction);

Even better would be a version where you could join multiple mapping functions. On the fly one could maybe use a varargs for the mappingFunction. But I honestly don’t know how that would be possible with Generics. But that’s a different story.

The solution should also be able to use whatever Predicate that one could imagine. Equals is just an Example. Another example would be to check if a field from the object is present.

animalWithMotherStream = animalStream
    .filter(Optional::isPresent, Animal::getMother);

Does anyone now a cleaner Solution, or a library that does this already?


Solution

  • You can use Guava's Predicates.compose(), or create your own:

    public static <A, B> Predicate<A> compose(
            Predicate<B> predicate, Function<A, ? extends B> function) {
        return a -> predicate.test(function.apply(a));
    }
    

    Now just pass that into your filter:

    animalStream.filter(compose(Colors.GREEN::equals, Animal::getColor))
    

    As for the varargs concept, I doubt that's possible under Java's generics, unless they're all of the same type, in which case you'd just apply() each in a loop or reduce them with Function.andThen().