问题
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
.
回答1:
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.
回答2:
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();
}
回答3:
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"));
}
}
回答4:
A short answer: Never use the interface UnaryOperator<T>
, use Function<T, T>
instead. If you do that, the methods compose and andThen
will work as expected.
回答5:
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.
来源:https://stackoverflow.com/questions/24006489/reducing-a-list-of-unaryoperators-in-java-8