传统命令式编程风格与编程功能风格的区别

时间:2021-04-23 20:10:57

I have a problem statement here what I need to do it iterate over a list find the first integer which is greater than 3 and is even then just double it and return it.

我在这里有一个问题声明我需要做什么迭代一个列表找到第一个大于3的整数然后再加倍并返回它。

These are some methods to check how many operations are getting performed

这些是检查执行了多少操作的一些方法

public static boolean isGreaterThan3(int number){
        System.out.println("WhyFunctional.isGreaterThan3 " + number);
        return number > 3;
    }
    public static boolean isEven(int number){
        System.out.println("WhyFunctional.isEven " + number);
        return number % 2 == 0;
    }
    public static int doubleIt(int number){
        System.out.println("WhyFunctional.doubleIt " + number);
        return number << 1;
    }

with java 8 streams I could do it like

使用java 8流我可以这样做

List<Integer> integerList = Arrays.asList(1, 2, 3, 5, 4, 6, 7, 8, 9, 10);
integerList.stream()
           .filter(WhyFunctional::isGreaterThan3)
           .filter(WhyFunctional::isEven)
           .map(WhyFunctional::doubleIt)
           .findFirst();

and the output is

而输出是

WhyFunctional.isGreaterThan3 1
WhyFunctional.isGreaterThan3 2
WhyFunctional.isGreaterThan3 3
WhyFunctional.isGreaterThan3 5
WhyFunctional.isEven 5
WhyFunctional.isGreaterThan3 4
WhyFunctional.isEven 4
WhyFunctional.doubleIt 4
Optional[8]

so total 8 operations.

总共8次操作。

And with imperative style or before java8 I could code it like

在命令式风格或java8之前,我可以编写代码

for (Integer integer : integerList) {
            if(isGreaterThan3(integer)){
                if(isEven(integer)){
                    System.out.println(doubleIt(integer));
                    break;
                }
            }
        }

and the output is

而输出是

WhyFunctional.isGreaterThan3 1
WhyFunctional.isGreaterThan3 2
WhyFunctional.isGreaterThan3 3
WhyFunctional.isGreaterThan3 5
WhyFunctional.isEven 5
WhyFunctional.isGreaterThan3 4
WhyFunctional.isEven 4
WhyFunctional.doubleIt 4
8

and operations are same. So my question is what difference does it make if I am using streams rather traditional for loop.

和操作是一样的。所以我的问题是,如果我使用流而不是传统的循环,它会有什么不同。

5 个解决方案

#1


7  

Stream API introduces the new idea of streams which allows you to decouple the task in a new way. For example, based on your task it's possible that you want to do different things with the doubled even numbers greater than three. In some place you want to find the first one, in other place you need 10 such numbers, in third place you want to apply more filtering. You can encapsulate the algorithm of finding such numbers like this:

Stream API引入了流的新思想,允许您以新的方式分离任务。例如,根据您的任务,您可能希望使用大于三的偶数偶数来执行不同的操作。在某些地方你想要找到第一个,在其他地方你需要10个这样的数字,在第三个地方你想要应用更多的过滤。您可以封装查找此类数字的算法,如下所示:

static IntStream numbers() {
    return IntStream.range(1, Integer.MAX_VALUE)
                    .filter(WhyFunctional::isGreaterThan3)
                    .filter(WhyFunctional::isEven)
                    .map(WhyFunctional::doubleIt);
}

Here it is. You've just created an algorithm to generate such numbers (without generating them) and you don't care how they will be used. One user might call:

这里是。您刚刚创建了一个算法来生成这样的数字(不生成它们),您不关心它们将如何使用。一个用户可能会致电:

int num = numbers().findFirst().get();

Other user might need to get 10 such numbers:

其他用户可能需要获得10个这样的数字:

int[] tenNumbers = numbers().limit(10).toArray();

Third user might want to find the first matching number which is also divisible by 7:

第三个用户可能想要找到第一个匹配的数字,该数字也可被7整除:

int result = numbers().filter(n -> n % 7 == 0).findFirst().get();

It would be more difficult to encapsulate the algorithm in traditional imperative style.

将算法封装在传统的命令式中会更加困难。

In general the Stream API is not about the performance (though parallel streams may work faster than traditional solution). It's about the expressive power of your code.

通常,Stream API与性能无关(尽管并行流可能比传统解决方案更快)。这是关于代码的表现力。

#2


6  

The imperative style complects the computational logic with the mechanism used to achieve it (iteration). The functional style, on the other hand, decomplects the two. You code against an API to which you supply your logic and the API has the freedom to choose how and when to apply it.

命令式样式通过用于实现它的机制(迭代)来补充计算逻辑。另一方面,功能风格将两者结合起来。您根据提供逻辑的API进行编码,API可以*选择应用方式和时间。

In particular, the Streams API has two ways how to apply the logic: either sequentially or in parallel. The latter is actually the driving force behind the introduction of both lambdas and the Streams API itself into Java.

特别是,Streams API有两种应用逻辑的方法:顺序或并行。后者实际上是将lambdas和Streams API本身引入Java的驱动力。

The freedom to choose when to perform computation gives rise to laziness: whereas in the imperative style you have a concrete collection of data, in the functional style you can have a collection paired with logic to transform it. The logic can be applied "just in time", when you actually consume the data. This further allows you to spread the building up of computation: each method can receive a stream and apply a further step of computation on it, or it can consume it in different ways (by collecting into a list, by finding just the first item and never applying computation to the rest, but calculating an aggregate value, etc.).

选择何时执行计算的*会导致懒惰:而在命令式样式中,您拥有一个具体的数据集合,在功能样式中,您可以将一个集合与逻辑进行转换。当您实际使用数据时,逻辑可以“及时”应用。这进一步允许您扩展计算的构建:每个方法可以接收流并在其上应用进一步的计算步骤,或者它可以以不同的方式使用它(通过收集到列表,通过仅查找第一个项目和永远不会将计算应用于其余部分,而是计算总值等)。

As a particular example of the new opportunities offered by laziness, I was able to write a Spring MVC controller which returned a Stream whose data source was a database—and at the time I return the stream, the data is still in the database. Only the View layer will pull the data, implicitly applying the transformation logic it has no knowledge of, never having to retain more than a single stream element in memory. This converted a solution which classically had O(n) space complexity into O(1), thus becoming insensitive to the size of the result set.

作为懒惰提供的新机会的一个特例,我能够编写一个Spring MVC控制器,它返回一个Stream,其数据源是一个数据库 - 当我返回流时,数据仍在数据库中。只有View层才能提取数据,隐式应用它不知道的转换逻辑,永远不必在内存中保留多个流元素。这将经典地具有O(n)空间复杂度的解决方案转换为O(1),从而对结果集的大小变得不敏感。

#3


3  

Using the Stream API you are describing an operation instead of implementing it. One commonly known advantage of letting the Stream API implement the operation is the option of using different execution strategies like parallel execution (as already said by others).

使用Stream API,您将描述操作而不是实现它。让Stream API实现操作的一个众所周知的优点是可以选择使用不同的执行策略,如并行执行(正如其他人已经说过的)。

Another feature which seems to be a bit underestimated is the possibility to alter the operation itself in a way that is impossible to do in an imperative programming style as that would imply modifying the code:

另一个似乎有点被低估的特性是可能以命令式编程风格无法改变操作本身,因为这意味着修改代码:

IntStream is=IntStream.rangeClosed(1, 10).filter(i -> i > 4);
if(evenOnly) is=is.filter(i -> (i&1)==0);
if(doubleIt) is=is.map(i -> i<<1);
is.findFirst().ifPresent(System.out::println);

Here, the decision whether to filter out odd numbers or double the result is made before the terminal operation is commenced. In an imperative programming you either have to recheck the flags within the loop or code multiple alternative loops. It should be mentioned that checking such conditions within a loop isn’t that bad on today’s JVM as the optimizer is capable of moving them out of the loop at runtime, so coding multiple loops is usually unnecessary.

这里,在终端操作开始之前做出是否过滤掉奇数或加倍结果的决定。在命令式编程中,您必须重新检查循环中的标志或编写多个替代循环。应该提到的是,在循环中检查这样的条件在今天的JVM上并不是那么糟糕,因为优化器能够在运行时将它们移出循环,因此通常不需要编码多个循环。

But consider the following example:

但请考虑以下示例:

Stream<String> s = Stream.of("java8 streams", "are cool");
if(singleWords) s=s.flatMap(Pattern.compile("\\s")::splitAsStream);
s.collect(Collectors.groupingBy(str->str.charAt(0)))
 .forEach((k,v)->System.out.println(k+" => "+v));

Since flatMap is the equivalent of a nested loop, coding the same in an imperative style isn’t that simple any more as we have either a simple loop or a nested loop based on a runtime value. Usually, you have to resort to splitting the code into multiple methods if you want to share it between both kind of loops.

由于flatMap相当于嵌套循环,因此我们有一个基于运行时值的简单循环或嵌套循环,因此在命令式样式中编码相同并不那么简单。通常,如果要在两种循环之间共享代码,则必须使用将代码拆分为多个方法。

I already encountered a real-life example where the composition of a complex operation had multiple conditional flatMap steps. The equivalent imperative code is insane…

我已经遇到过一个现实生活中的例子,其中复杂操作的组合有多个条件flatMap步骤。等效的命令性代码是疯狂的......

#4


1  

1) Functional approach allows more declarative way of programming: you just provide a list of functions to apply and don't need to write iterations manually, so your code is more consine sometimes.

1)功能方法允许更多的声明性编程方式:您只需提供要应用的函数列表,而不需要手动编写迭代,因此您的代码有时会更加健全。

2) If you switch to parallel stream (https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html) it will be possible to automatically convert your program to parallel and execute it faster. It is possbile because you don't explicitly code iteration, just list what functions to apply, so compiler/runtime may parallel it.

2)如果切换到并行流(https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html),则可以自动将程序转换为并行并更快地执行。它是可能的,因为您没有显式地编写迭代代码,只列出要应用的函数,因此编译器/运行时可以将它并行化。

#5


1  

In this simple example, there is little difference, and the JVM will try to do the same amount of work in each case.

在这个简单的例子中,没有什么区别,JVM将尝试在每种情况下执行相同数量的工作。

Where you start to see a difference is in more complicated examples like

你开始看到差异的地方是更复杂的例子

integerList.parallelStream()

making the code concurrent for a loop is much harder. Note: you wouldn't actually do this as the overhead would to high and you only want the first element.

使代码并发进行循环要困难得多。注意:你实际上不会这样做,因为开销会很高,你只需要第一个元素。

BTW The first example returns the result and the second prints.

BTW第一个示例返回结果,第二个示例返回打印。

#1


7  

Stream API introduces the new idea of streams which allows you to decouple the task in a new way. For example, based on your task it's possible that you want to do different things with the doubled even numbers greater than three. In some place you want to find the first one, in other place you need 10 such numbers, in third place you want to apply more filtering. You can encapsulate the algorithm of finding such numbers like this:

Stream API引入了流的新思想,允许您以新的方式分离任务。例如,根据您的任务,您可能希望使用大于三的偶数偶数来执行不同的操作。在某些地方你想要找到第一个,在其他地方你需要10个这样的数字,在第三个地方你想要应用更多的过滤。您可以封装查找此类数字的算法,如下所示:

static IntStream numbers() {
    return IntStream.range(1, Integer.MAX_VALUE)
                    .filter(WhyFunctional::isGreaterThan3)
                    .filter(WhyFunctional::isEven)
                    .map(WhyFunctional::doubleIt);
}

Here it is. You've just created an algorithm to generate such numbers (without generating them) and you don't care how they will be used. One user might call:

这里是。您刚刚创建了一个算法来生成这样的数字(不生成它们),您不关心它们将如何使用。一个用户可能会致电:

int num = numbers().findFirst().get();

Other user might need to get 10 such numbers:

其他用户可能需要获得10个这样的数字:

int[] tenNumbers = numbers().limit(10).toArray();

Third user might want to find the first matching number which is also divisible by 7:

第三个用户可能想要找到第一个匹配的数字,该数字也可被7整除:

int result = numbers().filter(n -> n % 7 == 0).findFirst().get();

It would be more difficult to encapsulate the algorithm in traditional imperative style.

将算法封装在传统的命令式中会更加困难。

In general the Stream API is not about the performance (though parallel streams may work faster than traditional solution). It's about the expressive power of your code.

通常,Stream API与性能无关(尽管并行流可能比传统解决方案更快)。这是关于代码的表现力。

#2


6  

The imperative style complects the computational logic with the mechanism used to achieve it (iteration). The functional style, on the other hand, decomplects the two. You code against an API to which you supply your logic and the API has the freedom to choose how and when to apply it.

命令式样式通过用于实现它的机制(迭代)来补充计算逻辑。另一方面,功能风格将两者结合起来。您根据提供逻辑的API进行编码,API可以*选择应用方式和时间。

In particular, the Streams API has two ways how to apply the logic: either sequentially or in parallel. The latter is actually the driving force behind the introduction of both lambdas and the Streams API itself into Java.

特别是,Streams API有两种应用逻辑的方法:顺序或并行。后者实际上是将lambdas和Streams API本身引入Java的驱动力。

The freedom to choose when to perform computation gives rise to laziness: whereas in the imperative style you have a concrete collection of data, in the functional style you can have a collection paired with logic to transform it. The logic can be applied "just in time", when you actually consume the data. This further allows you to spread the building up of computation: each method can receive a stream and apply a further step of computation on it, or it can consume it in different ways (by collecting into a list, by finding just the first item and never applying computation to the rest, but calculating an aggregate value, etc.).

选择何时执行计算的*会导致懒惰:而在命令式样式中,您拥有一个具体的数据集合,在功能样式中,您可以将一个集合与逻辑进行转换。当您实际使用数据时,逻辑可以“及时”应用。这进一步允许您扩展计算的构建:每个方法可以接收流并在其上应用进一步的计算步骤,或者它可以以不同的方式使用它(通过收集到列表,通过仅查找第一个项目和永远不会将计算应用于其余部分,而是计算总值等)。

As a particular example of the new opportunities offered by laziness, I was able to write a Spring MVC controller which returned a Stream whose data source was a database—and at the time I return the stream, the data is still in the database. Only the View layer will pull the data, implicitly applying the transformation logic it has no knowledge of, never having to retain more than a single stream element in memory. This converted a solution which classically had O(n) space complexity into O(1), thus becoming insensitive to the size of the result set.

作为懒惰提供的新机会的一个特例,我能够编写一个Spring MVC控制器,它返回一个Stream,其数据源是一个数据库 - 当我返回流时,数据仍在数据库中。只有View层才能提取数据,隐式应用它不知道的转换逻辑,永远不必在内存中保留多个流元素。这将经典地具有O(n)空间复杂度的解决方案转换为O(1),从而对结果集的大小变得不敏感。

#3


3  

Using the Stream API you are describing an operation instead of implementing it. One commonly known advantage of letting the Stream API implement the operation is the option of using different execution strategies like parallel execution (as already said by others).

使用Stream API,您将描述操作而不是实现它。让Stream API实现操作的一个众所周知的优点是可以选择使用不同的执行策略,如并行执行(正如其他人已经说过的)。

Another feature which seems to be a bit underestimated is the possibility to alter the operation itself in a way that is impossible to do in an imperative programming style as that would imply modifying the code:

另一个似乎有点被低估的特性是可能以命令式编程风格无法改变操作本身,因为这意味着修改代码:

IntStream is=IntStream.rangeClosed(1, 10).filter(i -> i > 4);
if(evenOnly) is=is.filter(i -> (i&1)==0);
if(doubleIt) is=is.map(i -> i<<1);
is.findFirst().ifPresent(System.out::println);

Here, the decision whether to filter out odd numbers or double the result is made before the terminal operation is commenced. In an imperative programming you either have to recheck the flags within the loop or code multiple alternative loops. It should be mentioned that checking such conditions within a loop isn’t that bad on today’s JVM as the optimizer is capable of moving them out of the loop at runtime, so coding multiple loops is usually unnecessary.

这里,在终端操作开始之前做出是否过滤掉奇数或加倍结果的决定。在命令式编程中,您必须重新检查循环中的标志或编写多个替代循环。应该提到的是,在循环中检查这样的条件在今天的JVM上并不是那么糟糕,因为优化器能够在运行时将它们移出循环,因此通常不需要编码多个循环。

But consider the following example:

但请考虑以下示例:

Stream<String> s = Stream.of("java8 streams", "are cool");
if(singleWords) s=s.flatMap(Pattern.compile("\\s")::splitAsStream);
s.collect(Collectors.groupingBy(str->str.charAt(0)))
 .forEach((k,v)->System.out.println(k+" => "+v));

Since flatMap is the equivalent of a nested loop, coding the same in an imperative style isn’t that simple any more as we have either a simple loop or a nested loop based on a runtime value. Usually, you have to resort to splitting the code into multiple methods if you want to share it between both kind of loops.

由于flatMap相当于嵌套循环,因此我们有一个基于运行时值的简单循环或嵌套循环,因此在命令式样式中编码相同并不那么简单。通常,如果要在两种循环之间共享代码,则必须使用将代码拆分为多个方法。

I already encountered a real-life example where the composition of a complex operation had multiple conditional flatMap steps. The equivalent imperative code is insane…

我已经遇到过一个现实生活中的例子,其中复杂操作的组合有多个条件flatMap步骤。等效的命令性代码是疯狂的......

#4


1  

1) Functional approach allows more declarative way of programming: you just provide a list of functions to apply and don't need to write iterations manually, so your code is more consine sometimes.

1)功能方法允许更多的声明性编程方式:您只需提供要应用的函数列表,而不需要手动编写迭代,因此您的代码有时会更加健全。

2) If you switch to parallel stream (https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html) it will be possible to automatically convert your program to parallel and execute it faster. It is possbile because you don't explicitly code iteration, just list what functions to apply, so compiler/runtime may parallel it.

2)如果切换到并行流(https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html),则可以自动将程序转换为并行并更快地执行。它是可能的,因为您没有显式地编写迭代代码,只列出要应用的函数,因此编译器/运行时可以将它并行化。

#5


1  

In this simple example, there is little difference, and the JVM will try to do the same amount of work in each case.

在这个简单的例子中,没有什么区别,JVM将尝试在每种情况下执行相同数量的工作。

Where you start to see a difference is in more complicated examples like

你开始看到差异的地方是更复杂的例子

integerList.parallelStream()

making the code concurrent for a loop is much harder. Note: you wouldn't actually do this as the overhead would to high and you only want the first element.

使代码并发进行循环要困难得多。注意:你实际上不会这样做,因为开销会很高,你只需要第一个元素。

BTW The first example returns the result and the second prints.

BTW第一个示例返回结果,第二个示例返回打印。