【问题标题】:Java 8 Streams & lambdas maintaining strict FPJava 8 Streams 和 lambdas 维护严格的 FP
【发布时间】:2017-06-22 11:08:23
【问题描述】:

Java 8 lambda 在许多情况下都非常有用,可以以紧凑的方式以 FP 方式实现代码。
但是在某些情况下,我们可能不得不访问/改变外部状态,根据 FP 实践,这不是一个好的实践。
(因为 Java 8 函数式接口有严格的输入和输出签名,我们不能传递额外的参数)

例如:

class Country{
        List<State> states;
    }
    class State{
        BigInt population;
        String capital;
    }

    class Main{
        List<Country> countries;

        //code to fill
    }

假设用例是获取所有国家/地区所有州的所有首都和所有人口的列表

正常实施:

List<String> capitals = new ArrayList<>();
BigInt population = new BigInt(0);

for(Country country:countries){
    for(State state:states){
        capitals.add(state.capital);
        population.add(state.population)
    }
}

如何以更优化的方式使用 Java 8 Streams 实现相同的功能?

Stream<State> statesStream = countries.stream().flatMap(country->country.getStates());

    capitals = statesStream.get().collect(toList());
    population = statesStream.get().reduce((pop1,pop2) -> return pop1+pop2);

但是上面的实现不是很有效。任何使用 Java 8 Streams 操作多个集合的其他更好的方法

【问题讨论】:

  • 你的代码不是“效率不高”,你的代码根本不起作用。 Stream 上没有 get() 方法,并且流不会神奇地为您选择正确的属性。

标签: java lambda functional-programming java-8 java-stream


【解决方案1】:

如果您想在一个管道中收集多个结果,您应该创建一个结果容器和一个自定义 Collector

class MyResult {
  private BigInteger population = BigInteger.ZERO;
  private List<String> capitals = new ArrayList<>();

  public void accumulate(State state) {
    population = population.add(state.population);
    capitals.add(state.capital);
  }

  public MyResult merge(MyResult other) {
    population = population.add(other.population);
    capitals.addAll(other.capitals);
    return this;
  }
}
MyResult result = countries.stream()
  .flatMap(c -> c.getStates().stream())
  .collect(Collector.of(MyResult::new, MyResult::accumulate, MyResult::merge));

BigInteger population = result.population;
List<String> capitals = result.capitals;

或者像你一样串流两次。

【讨论】:

  • 但是在您浪费时间实施这样的Collector 之前,您应该验证有关效率的初始声明。迭代一个列表并不昂贵,而同时做两件完全不同的事情可能会阻碍 JIT 的优化潜力……
  • 我认为这种情况下的效率意味着不流两次。由于此操作不会花费很长时间。
  • 是的,但是“流式传输两次”是什么意思?它只是意味着在没有进一步参考的情况下迭代 OP 声称“效率不高”的源列表。就开发费用而言,两个流语句比此收集器简单得多,并且在执行速度方面,如前所述,没有明确的说法,但没有理由假设二合一收集器的性能会显着超过流式传输 - 两倍。
  • 我完全同意。 IMO OP 的问题是使用 Java 8 Streams 操作多个集合的任何其他/更好的方法
  • IMO 流式传输两次在意图方面更加模糊,特别是当您想要执行过滤器操作并且必须在两个管道中应用过滤器时。
【解决方案2】:

你只能消费一次流,所以你需要创建一个可以减少的聚合:

public class CapitalsAndPopulation {
  private List<String> capitals;
  private BigInt population;

  // constructors and getters omitted for conciseness

  public CapitalsAndPopulation merge(CapitalsAndPopulation other) {
    return new CapitalsAndPopulation(
      Lists.concat(this.capitals, other.capitals),
      this.population + other.population);
  }
}

然后你生产管道:

countries.stream()
  .flatMap(country->
    country.getStates()
      .stream())
  .map(state -> new CapitalsAndPopulation(Collections.singletonList(state.getCapital()), state.population))
  .reduce(CapitalsAndPopulation::merge);

这看起来如此丑陋的原因是你没有很好的语法来处理像元组或映射这样的结构,所以你需要创建类来让管道看起来不错......

【讨论】:

  • 构造函数在哪里?
  • 为了简洁起见,我省略了它。我已经编辑了我的答案以使其更加明显。
  • @kewne 你说得对,没有简单的方法可以即时创建单独的结构
【解决方案3】:

试试这个。

class Pair<T, U> {
    T first;
    U second;

    Pair(T first, U second) {
        this.first = first;
        this.second = second;
    }
}

Pair<List<String>, BigInteger> result = countries.stream()
    .flatMap(country -> country.states.stream())
    .collect(() -> new Pair<>(
            new ArrayList<>(),
            BigInteger.ZERO
        ),
        (acc, state) -> {
            acc.first.add(state.capital);
            acc.second = acc.second.add(state.population);
        },
        (a, b) -> {
            a.first.addAll(b.first);
            a.second = a.second.add(b.second);
        });

您可以使用AbstractMap.Entry&lt;K, V&gt; 代替Pair&lt;T, U&gt;

Entry<List<String>, BigInteger> result = countries.stream()
    .flatMap(country -> country.states.stream())
    .collect(() -> new AbstractMap.SimpleEntry<>(
            new ArrayList<>(),
            BigInteger.ZERO
        ),
        (acc, state) -> {
            acc.getKey().add(state.capital);
            acc.setValue(acc.getValue().add(state.population));
        },
        (a, b) -> {
            a.getKey().addAll(b.getKey());
            a.setValue(a.getValue().add(b.getValue()));
        });

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-02
    • 2020-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-19
    • 2020-01-18
    相关资源
    最近更新 更多