减少Java 8中的一元运算符列表

时间:2014-06-03 03:07:54

标签: java functional-programming java-8

在Java 8中减少UnaryOperator列表的首选方法是什么,直到它们代表一个我可以调用的UnaryOperator应用? 例如,我有以下

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);

}

但是此代码会在ClassCastException处抛出(MyFilter)f1.andThen(f2)。 我真的想要这个代码的效果到底:

MyObject o = obj;
for(MyFilter f:filters){
  o = f.apply(o);
}
return o;

但我也很好奇我们如何使用composeandThen将函数集合减少到一个函数。

5 个答案:

答案 0 :(得分:12)

使用composeandThen的问题在于它们内置于Function接口和类型 - 编译时和运行时类型 - 他们返回的函数是Function而不是UnaryOperator或者您定义的子接口。例如,假设我们有

UnaryOperator<String> a = s -> s + "bar";
UnaryOperator<String> b = s -> s + s;

有人可能会认为我们可以写

UnaryOperator<String> c = a.compose(b);

但这不起作用!相反,必须写

Function<String, String> c = a.compose(b);

为此,UnaryOperator必须提供andThencompose的协变覆盖。 (可以说这是API中的一个错误。)您在子接口中也这样做。或者,它很简单,可以手工写出lambda。例如,

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"));
}

打印出[foobar][foobar]。请注意,我使用了两个arg形式的reduce,以避免必须处理Optional

或者,如果您经常进行功能组合,则可以在自己的界面中重新实现所需的方法。这不太难。这些是基于java.util.Function中的实现,但我在此示例中使用的具体String类型替代了泛型。

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));
    }
}

这将使用如下:

MyOperator composite =
    list.stream()
        .reduce(MyOperator.identity(), (a, b) -> a.andThen(b));

我认为,是否填充界面以便编写andThen而不是嵌套的lambda是一种品味问题。

答案 1 :(得分:2)

MyFilterandThen继承Function方法,因此返回的类型为Function,无法转换为MyFilter。但由于它具有所需的函数签名,您可以使用lambda或方法引用创建MyFilter实例。

E.g。将(f1,f2)->(MyFilter)f1.andThen(f2)更改为(f1,f2)-> f1.andThen(f2)::apply

通过此更改,整个方法如下所示:

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);
}

但你应该重新考虑你的设计。没有必要让结果函数成为MyFilter的实例,事实上,即使输入也可以放宽,不仅仅接受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);
}

或者,使用Stuart Marks提示摆脱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);
}

为了完整起见,您可以在流上链接MyFilter而不是编写新功能:

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();
}

答案 2 :(得分:1)

简短回答:切勿使用界面UnaryOperator<T>,而是使用Function<T, T>。如果您这样做,那么这些方法将构成,andThen将按预期工作。

答案 3 :(得分:0)

可以通过在抽象方法上使用方法引用语法将功能接口转换为另一个功能接口。

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 :(得分:0)

您可以在进行归约之前对Function<?, ?>进行强制转换:

interface MyFilter extends UnaryOperator<MyObject>{};

Function<MyObject, MyObject> mf = filters.stream()
    .map(f -> (Function<MyObject, MyObject>) f)
    .reduce(Function.identity(), Function::compose);

这使您可以使用Function.identity()Function::compose方法,而无需在界面中重新实现它们(如建议的Stuart Marks)。

演员表始终是安全的,因为它向上/扩大了。