Java的函数式编程详解
作者:在下uptown
前言
虽然Jdk版本已经出到了20+,甚至IDEA创建maven默认项目Jdk版本都11了,但是你发任你发,我用Java8,最近别的组的同事要对接个需求,把代码仓库开给他之后反馈看不太明白代码。
用了这么久的Java8,我寻思这种话也好意思说出来吗,难道自己是PythonBoy出身就是看不懂Java的理由吗,身为一个合格的后端Boy不会还有人看不明白Java的函数式编程吧。
函数式编程
那什么是函数编程呢,说白了就是可以把函数作为参数传递给其他函数,亦或者将一个函数作为返回值从另一个函数中返回。
于是Jdk根据不同的场景提供了一些核心特性和函数式接口:
Consumer<T>:接受一个输入参数 T,并在方法内部执行操作,没有返回值。 Supplier<T>:不接受任何输入参数,提供一个结果 T。 Function<T, R>:接受一个输入参数 T,执行操作并返回一个结果 R。 Predicate<T>:接受一个输入参数 T,返回一个布尔值表示是否满足条件。 UnaryOperator<T>:接受一个输入参数 T,执行操作并返回一个与输入参数类型相同的结果。 BinaryOperator<T>:接受两个相同类型的输入参数 T,执行操作并返回一个相同类型的结果。
相信大家经常在业务代码中有过这样的场景,DAO层返回了一个Optional类,如果我们只想获得其中的某一个属性应该怎么写。
我们借助一个小菜鸡老弟的代码说,这段代码写的只能说一言难尽,按照他的想法,他只想拿到age字段,其实我们只需要借助map即可满足需求。
aa.map(Studnet::getAge) .orElseThrow( () -> throw new Exception("user not found") );
这里的orElseThrow其实就是传进去的一个方法,我们可以在方法里做些别的事,比如打印下信息之类的
aa.map(Student::getAge).orElseThrow(() -> { System.out.println("error! error!"); return new Exception("user not found"); });
这就是一个很简单的函数式编程例子,把函数作为参数,在另一个函数中调用。这里举一个真实的业务案例,根据高内聚低耦合的抽象思想,我们保存数据的时候做一层前置数据校验,不同的类有不同的检查字段和方法,比如Student类要检查年龄是否大于18岁。
public class Java8Test { public static void main(String[] args) { Student student1 = Student.builder().age(10).name("uptown").build(); savePeople(student1, (Student s1) -> { if (s1.getAge() < 18) { throw new RuntimeException("18岁禁!"); } }); } public static <T extends People> void savePeople(T t, Consumer<T> function) { function.accept(t); // 业务TODO } } @Data @Builder class People { } @Data @Builder class Student extends People { Integer age; String name; }
这样写是不是比叠加if语句优化很多。
闭包
熟练使用函数式编程的jym对这个概念肯定不陌生,闭包就是能够读取其他函数内部变量的函数,借助网上js闭包的例子。
function makeFunc() { var name = "Mozilla"; function displayName() { console.log(name); } return displayName; } var myFunc = makeFunc(); myFunc(); // "Mozilla"
可以通俗的想象成,你在房间里写了一段代码包含一个函数和一些变量。当你离开房间后,函数和变量仍然存在,它们就形成了一个闭包。当你再次进入房间时,你可以使用闭包中的函数来访问和修改之前已经修改过的变量,变量仍然记录着之前的状态。
那么Java中是否有闭包特性呢,答案是严格意义上说没有。
因为仔细想一下就发现在JVM里这套逻辑显然不符合,在栈中的变量函数结束后就会被清掉,怎么会记住之前的结果。但我们可以通过匿名内部类或lambad表达式实现,lambad本质上也是匿名内部类。
public static void main(String[] args) { final int i = 0; Supplier<Integer> sup = new Supplier<>(){ Integer get(){ return i; } }; }
为了保证正确性和一致性,JDK在语法上规定了lambad内部访问的局部变量必须是final
修饰的。因为lambda表达式本质上创建了一个闭包,捕获了外部的局部变量,并在Lambda表达式的函数体中使用这些变量。
这只是其中一点,变量修饰为final还有一些数据一致方面的问题,这里就不再赘述了。
以上就是Java的函数式编程详解的详细内容,更多关于Java函数式编程的资料请关注脚本之家其它相关文章!