Reducing a list of UnaryOperators in Java 8
It's possible to convert a functional interface to another functional interface by means of using a method reference syntax on its abstract method.
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
public class Example {
public static void main(String[] args) {
Stream<UnaryOperator<String>> stringMappers = Stream.of(
s -> s + "bar",
s -> "[" + s + "]",
s -> s + s
);
UnaryOperator<String> f = stringMappers.reduce(
s -> s,
(a, b) -> a.andThen(b)::apply
);
System.out.println(f.apply("foo"));
}
}
The problem with using compose
or andThen
is that they're built into the Function
interface and the type -- both compile-time and runtime types -- of the functions they return is Function
and not UnaryOperator
or a subinterface such as you've defined. For example, suppose we have
UnaryOperator<String> a = s -> s + "bar";
UnaryOperator<String> b = s -> s + s;
One might think we could write
UnaryOperator<String> c = a.compose(b);
but this doesn't work! Instead, one has to write
Function<String, String> c = a.compose(b);
For this to work, UnaryOperator
would have to provide covariant overrides of andThen
and compose
. (Arguably this is a bug in the API.) You'd do the same in your subinterface. Or, it's simple enough to write out the lambdas by hand. For example,
interface MyOperator extends UnaryOperator<String> { }
public static void main(String[] args) {
List<MyOperator> list =
Arrays.asList(s -> s + "bar",
s -> "[" + s + "]",
s -> s + s);
MyOperator composite =
list.stream()
.reduce(s -> s, (a, b) -> s -> b.apply(a.apply(s)));
System.out.println(composite.apply("foo"));
}
This prints out [foobar][foobar]
. Note that I've used the two-arg form of reduce
in order to avoid having to deal with Optional
.
Alternatively, if you're doing function composition a lot, you could reimplement the methods you need in your own interface. It's not too hard. These are based on the implementations in java.util.Function
but with the concrete String
type I've been using in this example substituted for the generics.
interface MyOperator extends UnaryOperator<String> {
static MyOperator identity() {
return s -> s;
}
default MyOperator andThen(MyOperator after) {
Objects.requireNonNull(after);
return s -> after.apply(this.apply(s));
}
default MyOperator compose(MyOperator before) {
Objects.requireNonNull(before);
return s -> this.apply(before.apply(s));
}
}
This would be used as follows:
MyOperator composite =
list.stream()
.reduce(MyOperator.identity(), (a, b) -> a.andThen(b));
Whether bulking up the interface in order to write andThen
instead of a nested lambda is a matter of taste, I guess.
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();
}