What is the preferred way of reducing a list of UnaryOperators in Java 8 till they represent one UnaryOperator that I can call apply on? For example I have the following
interface MyFilter extends UnaryOperator<MyObject>{};
public MyObject filterIt(List<MyFilter> filters,MyObject obj){
Optional<MyFilter> mf = filters
.stream()
.reduce( (f1,f2)->(MyFilter)f1.andThen(f2));
return mf.map(f->f.apply(obj)).orElse(obj);
}
But this code throws a ClassCastException
at (MyFilter)f1.andThen(f2)
.
I really want the effect of this code in the end:
MyObject o = obj;
for(MyFilter f:filters){
o = f.apply(o);
}
return o;
But I am also curious of how we can reduce a collection of functions to one function, using compose
or andThen
.
Interface UnaryOperator<T>Represents an operation on a single operand that produces a result of the same type as its operand. This is a specialization of Function for the case where the operand and result are of the same type. This is a functional interface whose functional method is Function. apply(Object) .
The BiFunction Interface is a part of the java.util.function package which has been introduced since Java 8, to implement functional programming in Java. It represents a function which takes in two arguments and produces a result.
Interface Consumer<T>Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects. This is a functional interface whose functional method is accept(Object) . Since: 1.8.
The unary operators require only one operand; they perform various operations such as incrementing/decrementing a value by one, negating an expression, or inverting the value of a boolean. The increment/decrement operators can be applied before (prefix) or after (postfix) the operand.
MyFilter
inherits the method andThen
from Function
and therefore the returned type is Function
and cannot be cast to MyFilter
. But since it has the desired function signature, you can create the MyFilter
instance using a lambda or method reference.
E.g. change (f1,f2)->(MyFilter)f1.andThen(f2)
to (f1,f2)-> f1.andThen(f2)::apply
.
With this change the entire method looks like:
public static MyObject filterIt(List<MyFilter> filters, MyObject obj) {
Optional<MyFilter> mf =
filters.stream().reduce( (f1,f2)-> f1.andThen(f2)::apply);
return mf.map(f->f.apply(obj)).orElse(obj);
}
But you should rethink your design. There is no need to have the resulting function to be an instance of MyFilter
, in fact, even the input can be relaxed to accept more than just List<MyFilter>
:
// will accept List<MyFilter> as input
public static MyObject filterIt(
List<? extends Function<MyObject,MyObject>> filters, MyObject obj) {
List<Function<MyObject,MyObject>> l=Collections.unmodifiableList(filters);
Optional<Function<MyObject,MyObject>> mf=l.stream().reduce(Function::andThen);
return mf.orElse(Function.identity()).apply(obj);
}
or, using Stuart Marks’ hint for getting rid of the Optional
:
// will accept List<MyFilter> as input
public static MyObject filterIt(
List<? extends Function<MyObject,MyObject>> filters,MyObject obj) {
List<Function<MyObject,MyObject>> l=Collections.unmodifiableList(filters);
return l.stream().reduce(Function.identity(), Function::andThen).apply(obj);
}
Just for completeness, alternatively you could chain your MyFilter
s on a stream rather than composing a new function:
public static MyObject filterIt2(List<MyFilter> filters,MyObject obj) {
Stream<MyObject> s=Stream.of(obj);
for(MyFilter f: filters) s=s.map(f);
return s.findAny().get();
}
You can do a cast to Function<?, ?>
before doing the reduction:
interface MyFilter extends UnaryOperator<MyObject>{};
Function<MyObject, MyObject> mf = filters.stream()
.map(f -> (Function<MyObject, MyObject>) f)
.reduce(Function.identity(), Function::compose);
This lets you use the Function.identity()
and Function::compose
methods, avoiding the need to reimplement them in your interface (like Stuart Marks suggested).
The cast is always safe because its up/widening.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With