【问题标题】:Reducing a list of UnaryOperators in Java 8减少 Java 8 中的一元运算符列表
【发布时间】:2019-11-29 06:33:44
【问题描述】:

在 Java 8 中减少 UnaryOperator 列表直到它们代表一个我可以调用 apply 的 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);

}

但是这段代码在(MyFilter)f1.andThen(f2) 处抛出了一个ClassCastException。 好想要这段代码最后的效果:

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

但我也很好奇我们如何使用composeandThen 将一组函数简化为一个函数。

【问题讨论】:

    标签: java functional-programming java-8


    【解决方案1】:

    使用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]。请注意,我使用了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 而增大接口是个人喜好问题。

    【讨论】:

    • 很好的答案,谢谢。两个参数减少形式的一个优点是摆脱了 Optional。
    【解决方案2】:

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

    例如将(f1,f2)-&gt;(MyFilter)f1.andThen(f2) 更改为(f1,f2)-&gt; 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&lt;MyFilter&gt;

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

    为了完整起见,您也可以将MyFilters 链接到一个流上,而不是编写一个新函数:

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

    【讨论】:

    • 也是一个伟大而完整的答案。您的最终代码是我正在寻找的替代方案,谢谢。
    • 快速提问,Collections.unmodifiableList 是否优化了 stream() 和 reduce() 比原始列表更不可变?
    • 不,我不希望有任何区别。它在这里仅用作从List&lt;? extends X&gt;List&lt;X&gt; 的类型安全转换。
    【解决方案3】:

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

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

    【讨论】:

    • 使用 '::apply' 的建议帮助了我。比首选答案中给出的建议更好,即放弃 UnaryOperator 并切换到 Function。
    【解决方案4】:

    您可以在减少之前对Function&lt;?, ?&gt; 进行强制转换:

    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 建议的那样)。

    演员阵容总是安全的,因为它向上/扩大。

    【讨论】:

    • 如果需要类型化引用,可以使用MyFilter mfTyped = mf::apply;Function&lt;T ,T&gt;UnaryOperator&lt;T&gt; 之间进行转换
    【解决方案5】:

    简短回答:不要使用接口UnaryOperator&lt;T&gt;,而是使用Function&lt;T, T&gt;。如果您这样做,则 compose 和 andThen 方法将按预期工作。

    【讨论】:

      猜你喜欢
      • 2015-09-07
      • 2015-12-14
      • 2019-11-09
      • 2018-11-08
      • 1970-01-01
      • 2016-04-08
      • 2017-09-27
      • 1970-01-01
      • 2013-02-22
      相关资源
      最近更新 更多