Java流和状态


使用Java 8流,似乎函数编程赢了,无状态和递归万岁!但是现实有点微妙:与软件编程一样,它取决于。我相信你的工具箱中的工具越多越好。

当你拥有的只是一把锤子时,一切看起来像钉子。

在函数编程中,每个函数都需要纯粹:输出仅取决于输入,并且没有副作用。
看看Stream.generate()有两种用法:

Stream.generate(Math::random);    
Stream.generate(() -> "Java");  

第一行是随机值产生,第二行是常量输出。

Stream.iterate()的示例:

Stream.iterate(0, i -> i + 1);


当下一项的计算很简单时,这种迭代很容易。当计算变得更复杂时,需要数据结构。

这是一个带有函数的例子,它计算索引的平方,以便之后可以求和:

Stream.iterate(new double[]{1, 1},
               pair -> new double[]{pair[0] + 1, Math.pow(pair[0] + 1, 2)});


我不确定这是最易读的代码片段。为了使它更清洁,让我们创建一个专用的结构:

public class Pair {

    public final int index;
    public final double value;

    public Pair(int index, double value) {
        this.index = index;
        this.value = value;
    }
}

Stream.iterate(new Pair(1, 1),
               pair -> new Pair(pair.index + 1, Math.pow(pair.index + 1, 2)));

这只是稍微好一点,因为计算逻辑仍然在lambda中“隐藏”。一种解决方案可能是让Pair计算成为下一个值:

public class Pair {

    public static final Pair SEED = new Pair(1, 1);

    public final int index;
    public final double value;

    public Pair(int index, double value) {
        this.index = index;
        this.value = value;
    }

    public Pair next() {
        return new Pair(index + 1, Math.pow(index + 1, 2));
    }
}

Stream.iterate(Pair.SEED, Pair::next);

我认为这是一个非常巧妙的解决方案。它也可以重复用于其他功能/系列/套件。

以下是阶乘函数的示例:

public class Factorial {

    public static final Factorial SEED = new Factorial(1, 1);

    public final int index;
    public final int value;

    public Factorial(int index, int value) {
        this.index = index;
        this.value = value;
    }

    public Factorial next() {
        return new Factorial(index + 1, value * index);
    }
}

Stream.iterate(Pair.SEED, Pair::next);

还有另一个斐波那契套件:

public class Fibonacci {

    public static final Fibonacci SEED = new Fibonacci(1, 1);

    public final int previous;
    public final int value;


    public Fibonacci(int previous, int value) {
        this.previous = previous;
        this.value = value;
    }

    public Fibonacci next() {
        return new Fibonacci(value, value + previous);
    }
}

Stream.iterate(Fibonacci.SEED, Fibonacci::next);

注意状态是如何引入的?状态使代码更容易阅读。

现在,让我们进一步推动事情。还记得Stream.generate()上面函数吗?使用正确的有状态Supplier,它可以替换Stream.iterate():

public class IncrementSupplier implements Supplier<Integer> {

    private int value;

    public IncrementSupplier(int seed) {                      
        this.value = seed;
    }

    @Override
    public Integer get() {
        return ++value;                                       
    }
}

Stream.iterate(0, i -> i + 1);                                
Stream.generate(new IncrementSupplier(0));      

上面两行是同样的。

开发者更多的是激情的生物,而不是理性的生物。这不是因为Java提供了函数编程,它现在不允许状态。虽然状态是并发编程上下文中的一个问题,但它对于更易读的代码可能是一个巨大的帮助。