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

eat*_*ode 4 java performance java-8 java-stream

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

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

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;
    }
Run Code Online (Sandbox Code Playgroud)

使用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();
Run Code Online (Sandbox Code Playgroud)

而输出是

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]
Run Code Online (Sandbox Code Playgroud)

总共8个操作.

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

for (Integer integer : integerList) {
            if(isGreaterThan3(integer)){
                if(isEven(integer)){
                    System.out.println(doubleIt(integer));
                    break;
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

而输出是

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
Run Code Online (Sandbox Code Playgroud)

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

Tag*_*eev 7

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

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

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

int num = numbers().findFirst().get();
Run Code Online (Sandbox Code Playgroud)

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

int[] tenNumbers = numbers().limit(10).toArray();
Run Code Online (Sandbox Code Playgroud)

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

int result = numbers().filter(n -> n % 7 == 0).findFirst().get();
Run Code Online (Sandbox Code Playgroud)

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

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


Mar*_*nik 6

命令式样式通过用于实现它的机制(迭代)来补充计算逻辑.功能性风格,在另一方面,decomplects两个.您根据提供逻辑的API进行编码,API可以自由选择应用方式时间.

特别地,该流API有两种方式如何应用逻辑:要么顺序或并行.后者实际上是将lambdas和Streams API本身引入Java的驱动力.

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

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

  • 我认为当你强调一个事实是你可以实现一个只包含中间步骤的操作并将一个`Stream`返回给一个任意调用者,即不知道确切的终端操作时,差异会变得更加明显.终端操作可以改变评估的工作方式,例如是懒惰的,而命令式编程需要知道终端操作,并且不同的终端操作通常需要完全不同的代码用于整个操作. (2认同)