Dario Viva
Dario Viva

Reputation: 267

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?

Upvotes: 5

Views: 1946

Answers (6)

Michele Mariotti
Michele Mariotti

Reputation: 7449

I'm a little late, but this has not been proposed yet.

StreamEx has been already mentioned, but its usage may vary ;)

greenAnimals = StreamEx.of(animalStream)
    .mapToEntry(Animal::getColor)
    .filterValues(Colors.GREEN::equals)
    .keys();

animalWithMotherStream = StreamEx.of(animalStream)
    .mapToEntry(Animal::getMother)
    .removeValues(Optional::isEmpty)
    .keys();

The general approach is to map an implicit 'Entry' from the streamed objects themselves (as keys) AND a property of those objects (as values). Then you can use whatever you want on the values and finally return the keys.

Upvotes: 0

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 28968

Operation Stream.filter() expects a Predicate which is a function producing a boolean result. This definition of the filter is very intuitive and self-contained, and there are no other flavors of filter (and I doubt if they will appear in the future).

However, you can create your implementation of Predicate and give it all the behavior you need. And as a Predicate, it would be eligible to be used in the filter.

Before introducing the implementation, I'll show some of the capabilities that can be given to such custom Predicate.

Stream<Animal> greenAnimals = animalStream
    .filter(
        MultiPred.ofOr(Animal::getColor, Color.WHITE::equals, Color.GREEN::equals)
            .or(
                MultiPred.of(Animal::getType, AnimalType.CARNIVORE::equals)
                .and(Animal::canFly) // we can chain custom Predicates with regular ones
            )
            .or(
                MultiPred.of(Animal::getType, AnimalType.HERBIVORE::equals)
                .and(MultiPred.of(Animal::getColor, Color.PURPLE::equals)
                    .or(Animal::canSwim)
                )
            )
    );

Here's a dummy class Animal and enums used in the example above:

public class Animal {
    private AnimalType type;
    private boolean canSwim;
    private boolean canFly;
    private Color color;
    
    // getters
}

public enum AnimalType {
    CARNIVORE, HERBIVORE
}

public enum Color {
    GREEN, WHITE, PURPLE
}

Implementation

You can provide a custom Predicate with any capabilities you require.

The following predicate expects exposes methods expecting a keyExtractor function and a predicate, or a group of predicates that has to be chained with either logical OR ||, or logical AND &&.

public class MultiPred<T, K> implements Predicate<T> {

    private final BiFunction<Function<T, K>, Predicate<K>, Predicate<T>>
        predicateProducer = (f, p) -> t -> p.test(f.apply(t));
    
    private final Predicate<T> p;
    
    private MultiPred(Function<T, K> keyExtractor,
                      Predicate<K> predicate) {
        
        this.p = predicateProducer.apply(keyExtractor, predicate);
    }
    
    @SafeVarargs
    public static <T, K> MultiPred<T, K> ofAnd(Function<T, K> keyExtractor,
                                               Predicate<K>... predicates) {
        
        return of(keyExtractor, k -> true, Predicate::and, predicates);
    }
    
    @SafeVarargs
    public static <T, K> MultiPred<T, K> ofOr(Function<T, K> keyExtractor,
                                              Predicate<K>... predicates) {
        
        return of(keyExtractor, k -> false, Predicate::or, predicates);
    }
    
    @SafeVarargs
    public static <T, K> MultiPred<T, K> of(Function<T, K> keyExtractor,
                                            Predicate<K> identity,
                                            BinaryOperator<Predicate<K>> op,
                                            Predicate<K>... predicates) {
        
        Objects.requireNonNull(predicates);
        
        Predicate<K> predicate = Arrays.stream(predicates).reduce(identity, op);
        
        return new MultiPred<>(keyExtractor, predicate);
    }
    
    public static <T, K> MultiPred<T, K> of(Function<T, K> keyExtractor,
                                            Predicate<K> predicate) {
        
        Objects.requireNonNull(keyExtractor);
        Objects.requireNonNull(predicate);
    
        return new MultiPred<>(keyExtractor, predicate);
    }
    
    @Override
    public boolean test(T t) {
        Objects.requireNonNull(t);
        return p.test(t);
    }
    
    @Override
    public Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return p.and(other);
    }
    
    @Override
    public Predicate<T> negate() {
        return p.negate();
    }
    
    @Override
    public Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return p.or(other);
    }
}

Utility Class for constructing Predicates

The logic from the class above can materialized as a utility class exposing a bunch static method for generating Predicates. It would preserve all the capacities shown in usage example at the beginning.

(Credits for this idea belong to @Holger and @shmosel, since he posted the static method producing a predicate earlier, hence the code shown below should be considered as built on the answer by @shmosel)

public static class MultiPred {

    private MultiPred() {}

    @SafeVarargs
    public static <T, K> Predicate<T> ofAnd(Function<T, K> keyExtractor,
                                            Predicate<K>... predicates) {
        
        return of(keyExtractor, k -> true, Predicate::and, predicates);
    }
    
    @SafeVarargs
    public static <T, K> Predicate<T> ofOr(Function<T, K> keyExtractor,
                                           Predicate<K>... predicates) {
        
        return of(keyExtractor, k -> false, Predicate::or, predicates);
    }
    
    @SafeVarargs
    public static <T, K> Predicate<T> of(Function<T, K> keyExtractor,
                                         Predicate<K> identity,
                                         BinaryOperator<Predicate<K>> op,
                                         Predicate<K>... predicates) {
        
        Objects.requireNonNull(predicates);
        
        Predicate<K> predicate = Arrays.stream(predicates).reduce(identity, op);
        
        return getPredicateProducer(keyExtractor, predicate);
    }
    
    public static <T, K> Predicate<T> of(Function<T, K> keyExtractor,
                                         Predicate<K> predicate) {
        
        Objects.requireNonNull(keyExtractor);
        Objects.requireNonNull(predicate);
        
        return getPredicateProducer(keyExtractor, predicate);
    }
    
    private static <T, K> Predicate<T> getPredicateProducer(Function<T, K> keyExtractor,
                                                            Predicate<K> predicate) {
        return t -> predicate.test(keyExtractor.apply(t));
    }
}

Upvotes: 1

shmosel
shmosel

Reputation: 50716

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().

Upvotes: 1

Nikolas
Nikolas

Reputation: 44368

Java 16

Yes, there is a solution using Stream#mapMulti as of Java 16.

animalStream
    .mapMulti((animal, consumer) -> {
        if (Colors.GREEN.equals(animal.getColor())) {  // condition
            consumer.accept(animal);                   // qualify the instance
        }
    })
    ...                                                // further operations

The provided Consumer<R> accepts the instance that is qualified based on your criteria.

Pros: The performance-wise advantage of this imperative approach is that you don't necessarily invoke the two Stream operations but just one, for example, a combination of Stream#map and Stream#filter can be substituted. Though the biggest advantage is that Consumer#accept can be invoked as many times as you want, so you can effectively increase a number of entries in a Stream.

Cons: However, you lost a bit of the declarative approach and if used only as on the snippet without further processing, it's worth using rather a simple for-loop or sticking with the filter operation (see below):

Older Java versions

Simply write the condition down to Stream#filter, it's a correct Stream usage:

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

Upvotes: 2

OneCricketeer
OneCricketeer

Reputation: 191681

filter accepts a Predicate, whose function can only return a boolean. There's no other method signature.

If you want to filter by all green animals, you'd use

animalStream
    .filter(a -> Colors.GREEN.equals(a.getColor()))

or

Predicate<Animal> isGreen = (a) -> Colors.GREEN.equals(a.getColor());
Stream<Animal> greenAnimals = animalStream.filter(isGreen);

Don't use map unless you want a Stream<COLOR>

join multiple mapping functions

You can chain them, rather than join - .stream().map().map(), but as you discovered, this does not preserve the original type.

Upvotes: 1

John Kugelman
John Kugelman

Reputation: 361565

StreamEx, a library that provides extended stream methods and classes, has filterBy:

public <K> StreamEx<T> filterBy​(Function<? super T,​? extends K> mapper, K value)

Returns a stream consisting of the elements of this stream for which the supplied mapper function returns the given value.

This method behaves like filter(t -> Objects.equals(value, mapper.apply(t))).

Upvotes: 1

Related Questions