Java 8流和lambda如何保持严格的FP(函数式编程)?

3
Java 8 Lambdas在许多情况下非常有用,以一种紧凑的方式以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流来操作多个集合?


1
你的代码不是“不够高效”,而是完全无法工作。Stream 上没有 get() 方法,流也不能神奇地为你选择正确的属性。 - Holger
3个回答

6
如果您想在一个流水线中收集多个结果,您应该创建一个结果容器和一个自定义的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;

或者像你以前那样,再次流式传输。

1
但在你浪费时间实现这样一个Collector之前,你应该验证一下关于效率的初始声明。遍历列表并不昂贵,而同时进行两个完全不同的操作可能会阻碍JIT的优化潜力... - Holger
1
我认为在这个上下文中,效率意味着不进行两次串流。由于此操作不会花费很长时间。 - Flown
1
是的,但“流式传输两次”是什么意思?这只是迭代源列表,OP声称这种方法“不太高效”,没有进一步的参考。在开发费用方面,两个流语句比这个收集器简单得多,在执行速度方面,正如所说,没有明确的陈述,但没有理由认为一个两合一的收集器会显着优于两次流式传输。 - Holger
我完全同意。在我看来,OP的问题是“是否有其他/更好的方法使用Java 8 Streams操作多个集合”。 - Flown
1
在我的看法中,流式处理两次在意图方面要复杂得多,特别是当您想进行过滤操作并且必须在两个管道中应用过滤器时。 - kewne

1

流只能被消费一次,因此您需要创建一个可被减少的聚合体:

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

这看起来很丑的原因是你没有像元组或映射这样的结构的良好语法,因此你需要创建类来使管道看起来漂亮...

构造函数在哪里? - user4910279
我为了简洁省略了它。我编辑了我的答案,使其更加明显。 - kewne
@kewne 你说得对,没有简单的方法可以动态创建一个独立的结构。 - Saisurya Kattamuri

1

试试这个。

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<K, V> 代替 Pair<T, U>
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()));
        });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接