Java 传统命令式编程与函数式编程的区别
我这里有一个问题陈述 我需要做的是遍历一个列表,找到第一个大于3的整数,然后将它加倍并返回它 以下是一些检查执行了多少操作的方法Java 传统命令式编程与函数式编程的区别,java,performance,java-8,java-stream,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 isE
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;
}
总共8次手术
使用命令式风格或在java8之前,我可以像
for (Integer integer : integerList) {
if(isGreaterThan3(integer)){
if(isEven(integer)){
System.out.println(doubleIt(integer));
break;
}
}
}
输出是
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]
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
和操作是一样的。所以我的问题是,如果我使用流而不是传统的for循环,它会有什么不同。1)函数方法允许更具声明性的编程方式:您只需提供一个要应用的函数列表,而不需要手动编写迭代,因此您的代码有时更为一致
2) 如果您切换到parallel stream(),则可以自动将程序转换为parallel并更快地执行。这是可能的,因为您没有显式地对迭代进行编码,只列出要应用的函数,因此编译器/运行时可能会对其进行并行处理。在这个简单的示例中,没有什么不同,JVM将尝试在每种情况下执行相同的工作量 在更复杂的示例中,您开始看到不同之处,如
integerList.parallelStream()
使循环的代码并发要困难得多。注意:您实际上不会这样做,因为开销会很高,您只需要第一个元素
顺便说一句,第一个示例返回结果,第二个打印。命令式样式使用实现它的机制(迭代)完成计算逻辑。另一方面,函数式风格对两者进行了反编译。您根据提供逻辑的API编写代码,API可以自由选择应用它的方式和时间
特别是,Streams API有两种方法来应用逻辑:顺序或并行。后者实际上是将lambdas和streamsapi本身引入Java的驱动力
选择何时执行计算的自由导致了懒惰:而在命令式样式中,您有一个具体的数据集合,而在函数式样式中,您可以有一个集合与逻辑配对来转换它。当您实际使用数据时,可以“及时”应用该逻辑。这进一步允许您扩展计算的构建:每个方法都可以接收一个流并对其应用进一步的计算步骤,或者可以以不同的方式使用它(通过收集到一个列表中,通过只查找第一项而不对其余项应用计算,而是计算聚合值,等等)
作为laziness提供的新机会的一个特殊示例,我能够编写一个Spring MVC控制器,它返回一个
流
,其数据源是一个数据库,在我返回流时,数据仍然在数据库中。只有视图层会提取数据,隐式地应用它不知道的转换逻辑,永远不必在内存中保留超过一个流元素。这将一个典型的具有O(n)空间复杂性的解决方案转换为O(1),从而对结果集的大小变得不敏感。流API引入了流的新思想,允许您以新的方式解耦任务。例如,根据您的任务,您可能希望使用大于3的双倍偶数做不同的事情。在某些地方你想找到第一个,在另一个地方你需要10个这样的数字,在第三个地方你想应用更多的过滤。您可以将查找此类数字的算法封装如下:
static IntStream numbers() {
return IntStream.range(1, Integer.MAX_VALUE)
.filter(WhyFunctional::isGreaterThan3)
.filter(WhyFunctional::isEven)
.map(WhyFunctional::doubleIt);
}
给你。您刚刚创建了一个算法来生成这样的数字(不生成它们),您不关心它们将如何使用。一个用户可能会呼叫:
int num = numbers().findFirst().get();
其他用户可能需要获得10个这样的数字:
int[] tenNumbers = numbers().limit(10).toArray();
第三个用户可能希望找到第一个匹配的数字,该数字也可以被7整除:
int result = numbers().filter(n -> n % 7 == 0).findFirst().get();
用传统的命令式风格封装算法会更加困难
一般来说,流API与性能无关(尽管并行流可能比传统解决方案工作得更快)。这是关于代码的表达能力。使用
流
API,您描述的是一个操作,而不是实现它。让流
API实现操作的一个众所周知的优点是可以选择使用不同的执行策略,比如并行执行(正如其他人已经说过的)
另一个似乎有点被低估的特性是,有可能以命令式编程风格无法实现的方式更改操作本身,因为这意味着修改代码:
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);
IntStream is=IntStream.rangeClosed(1,10).filter(i->i>4);
如果(evenOnly)is=is.filter(i->(i&1)==0);
如果(doubleIt)is=is.map(i->issystem.out.println(k+“=>”+v));
由于flatMap
相当于嵌套循环,因此以命令式样式编码相同的循环不再那么简单,因为我们有一个简单循环或基于运行时值的嵌套循环。通常,如果要在两种循环之间共享代码,就必须将代码拆分为多个方法
我已经遇到了一个实际示例,其中复杂操作的组合有多个条件
flatMap
步骤。等价的命令式代码是疯狂的…传统的方法描述了您的操作方式,声明式描述了您需要的结果。@TimBiegeleisen,您错了。字节码非常非常不同。这一定是因为函数方法不同,必须有调用的动态指令,因为我们在这里使用lamda。@SashaSalauyou“how/what”二分法只是关于描述的级别:即使使用FP,您也会说如何实现更高的目标。例如,what=“寻找最佳业务前景”,how=“fi
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);
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));