【问题标题】:Java 8 Functional Programming avoid if conditionalJava 8 函数式编程避免条件
【发布时间】:2021-01-11 03:15:27
【问题描述】:

你如何使用纯函数式编程(没有 if 条件)来完成下面的 transform() 方法的等效操作。

Meta:我很感激标题编辑,我不知道如何用“功能性”来表达这个问题

public class Playground {

    private static Optional<Map<String,Integer>> transform(List<Tuple<String,Optional<Integer>>> input) {
        if (input.stream().anyMatch(t->t.second.isEmpty())) return Optional.empty();

        Map<String, Integer> theMap = input.stream()
                .map(t -> new Tuple<>(t.first, t.second.get()))
                .collect(Collectors.groupingBy(
                        t1 -> t1.first,
                        Collectors.mapping(t2 -> t2.second, toSingle())));
        return Optional.of(theMap);
    }

    @Test
    public void collect()  {
        List<Tuple<String,Optional<Integer>>> input1 = new ArrayList<>();
        input1.add(new Tuple<>("foo", Optional.of(1)));
        input1.add(new Tuple<>("bar", Optional.empty()));

        Optional<Map<String,Integer>> result1 = transform(input1);

        assertTrue(result1.isEmpty());

        List<Tuple<String,Optional<Integer>>> input2 = new ArrayList<>();
        input2.add(new Tuple<>("foo", Optional.of(1)));
        input2.add(new Tuple<>("bar", Optional.of(2)));

        Optional<Map<String,Integer>> result2 = transform(input2);

        assertTrue(result2.isPresent());
        assertEquals((int)1, (int)result2.get().get("foo"));
        assertEquals((int)2, (int)result2.get().get("bar"));
    }

    private static class Tuple<T1,T2> {
        public T1 first;
        public T2 second;
        public Tuple(T1 first, T2 second) {
            this.first = first;
            this.second = second;
        }
    }

    public static <T> Collector<T, ?, T> toSingle() {
        return Collectors.collectingAndThen(
                Collectors.toList(),
                list ->  list.get(0)
        );
    }
}

【问题讨论】:

  • 唯一的if 语句的目的似乎是在两个替代返回值之间进行选择。我不确定它是否符合您的标准,但应该可以改用三元运算符。
  • 或者,您应该能够强制Optional.filter() 完成if 的工作,然后在您真正想要执行计算的情况下跟进Optional.map()
  • 你可以简单地使用.collect(Collectors.toMap(t -&gt; t.first, t -&gt; t.second.get(), (a,b) -&gt; a))而不是.map(t -&gt; new Tuple&lt;&gt;(t.first, t.second.get())) .collect(Collectors.groupingBy(t1 -&gt; t1.first, Collectors.mapping(t2 -&gt; t2.second, toSingle())))
  • @Holger 谢谢你,一个有用的简化!

标签: java java-8 functional-programming


【解决方案1】:

虽然我的解决方案不能满足你的结果,但我可以提供一个带有三元运算符的解决方案

private static Map<String, Integer> transform(List<Tuple<String, Optional<Integer>>> input) {
    return input.stream().anyMatch(t -> t.second.isEmpty()) ? Collections.emptyMap() :
            input.stream()
                    .map(t -> new Tuple<>(t.first, t.second.get()))
                    .collect(Collectors.groupingBy(
                            t1 -> t1.first,
                            Collectors.mapping(t2 -> t2.second, toSingle())));
}

【讨论】:

  • 如果迭代两次不是对 OP 产生重大影响的问题,我仍然建议遵循方法签名以摆脱 Optional Wrapped Map。
  • 完全同意你的观点,但这里的问题是找到函数式编程的解决方案
【解决方案2】:

这可能对你有用:

  private static Optional<Map<String, Integer>> transform(
      List<Tuple<String, Optional<Integer>>> input) {
    return Optional.of(input)
        .filter(t -> t.stream().allMatch(a -> a.second.isPresent()))
        .map(
            in ->
                in.stream()
                    .filter(t -> t.second.isPresent())
                    .map(t -> new Tuple<>(t.first, t.second.get()))
                    .collect(
                        Collectors.groupingBy(
                            t1 -> t1.first, Collectors.mapping(t2 -> t2.second, toSingle()))));
  }

【讨论】:

  • 这就是答案,还是很接近的。有没有办法避免流的双重迭代? (想象方法参数的类型是 Stream 而不是 List。
  • 不,我想不出一个干净的解决方案来解决这样的短路问题。我会采用两步法或通过早期回报的良好旧循环。
  • 谢谢,我相信这正是 Optional::filter 的目的。
【解决方案3】:

“纯函数式编程”不一定是质量的标志,本身也不是目的。

如果您想让代码更简单、更高效,这可能包括摆脱 if 条件,尤其是因为它对源数据进行第二次迭代,您可以通过多种方式实现。例如

private static <K,V> Optional<Map<K,V>> transform(List<Tuple<K,Optional<V>>> input) {
    final class AbsentValue extends RuntimeException {
        AbsentValue() { super(null, null, false, false); }
    }

    try {
        return Optional.of(input.stream().collect(Collectors.toMap(
            t1 -> t1.first,
            t2 -> t2.second.orElseThrow(AbsentValue::new),
            (first,next) -> first)));
    } catch(AbsentValue av) {
        return Optional.empty();
    }
}

当空选项确实是例外情况时,您可以通过方法合同的异常部分进行标记,例如

public static class AbsentValueException extends RuntimeException {

}
private static <K,V> Map<K,V> transform(List<Tuple<K,Optional<V>>> input)
    throws AbsentValueException {

    return input.stream().collect(Collectors.toMap(
        t1 -> t1.first,
        t2 -> t2.second.orElseThrow(AbsentValueException::new),
        (first,next)->first));
}
@Test(expected = AbsentValueException.class)
public void collect1() {
    List<Tuple<String,Optional<Integer>>> input1 = new ArrayList<>();
    input1.add(new Tuple<>("foo", Optional.of(1)));
    input1.add(new Tuple<>("bar", Optional.empty()));

    Map<String,Integer> result1 = transform(input1);
}

@Test
public void collect2() {
    List<Tuple<String,Optional<Integer>>> input2 = new ArrayList<>();
    input2.add(new Tuple<>("foo", Optional.of(1)));
    input2.add(new Tuple<>("bar", Optional.of(2)));

    Map<String,Integer> result2 = transform(input2);

    assertEquals((int)1, (int)result2.get("foo"));
    assertEquals((int)2, (int)result2.get("bar"));
}

最好不要一开始就将可选项放入元组列表中。

【讨论】:

  • “纯函数式编程不一定是质量的标志,本身也不是目的” - 是的。此外,太阳从东方升起,在西方落下。水是湿的。
  • James Gosling 等人在“Java 编程语言”一书中很早的时候就明确表示异常并不意味着用于控制流。出于类似的原因,我也会检查您的假设,它更有效。
  • @GarrettSmith 好吧,James Gosling 肯定从来没有说过你应该使用Optional 进行控制流。正如我在回答中所说,最好不要一开始就将可选项放入元组列表中。如果您修复了那里的缺陷,则无需在以后处理这种情况。问题是,在你的元组中有空的选项是否是一种特殊情况。您不想在这种情况下恢复任何价值这一事实表明它们异常的。并且一位专家确实分析了性能:shipilev.net/blog/2014/exceptional-performance
猜你喜欢
  • 2020-10-02
  • 2018-07-01
  • 2022-07-17
  • 1970-01-01
  • 2015-08-01
  • 2013-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多