Reputation: 267
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
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
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
}
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);
}
}
The logic from the class above can materialized as a utility class exposing a bunch static
method for generating Predicate
s. 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
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
Reputation: 44368
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):
Simply write the condition down to Stream#filter
, it's a correct Stream usage:
animalStream
.filter(animal -> Colors.GREEN.equals(animal.getColor()))
...
Upvotes: 2
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
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