1.引子
做java开发的小伙伴们,从jdk8起,都知道了并且喜欢写一些函数式编程风格的代码,那么今天,我们具体来聊一聊函数式编程这个话题。
在编程范式的世界里,有三种编程范式
- 面向过程编程
- 面向对象编程
- 函数式编程
有人说,函数式编程真好!自从有了函数式编程,腰不疼,腿不酸,还不抽筋!比起面向对象编程风格的代码,简直一个简单优雅!
但是从内心底我们要认清一个事实,每一种编程范式,都是有它适合的场景,并不是说谁替代谁的问题。
我们可以从函数式编程的字面含义理解一下,在函数式编程中的 “函数” ,并不是我们定义一个类里面的函数(方法),它更倾向于说是数学函数,或者叫做表达式。即对于函数式编程来说,它是程序面向数学的更底层抽象,把计算过程描述为表达式,它更加适合于科学计算、统计分析、数据处理等场景。
当我们开发一些大型复杂业务系统的时候,还是面向对象编程会更加普适。当然代码设计实现的时候,写着面向对象编程风格的代码,顺带在合适的地方用上函数式编程,也还不错!做java开发的小伙伴,不就经常这么干吗?对吧
那么接下来,我就跟你聊一聊java中的函数式编程!
2.案例
2.1.示例代码
jdk在1.8版本中,引入了函数式编程。具体提供了三个新的语法概念
- Stream流,通过操作符"."支持多个函数之间的级联操作
- Lambda表达式,简化代码编写,让代码看起来简洁优雅
- Functional Interface函数接口,将函数封装成接口,实现将函数当做参数使用
看一段示例代码
package cn.edu.anan.fun;
import java.util.Optional;
import java.util.stream.Stream;
/**
* 函数式编程示例代码
*
* @author ThinkPad
* @version 1.0
* @date 2022/1/2 11:43
*/
public class FunctionDemo {
public static void main(String[] args) {
Optional<Integer> min = Stream.of("cat", "dog", "pig", "monster", "tiger", "dragLong")
.map(v -> v.length())
.filter(l -> l <= 3)
.min((o1, o2) -> o1 - o2);
System.out.println("计算结果:" + min.orElse(0));
}
}
// 执行结果
计算结果:3
上面的示例代码,即是java开发常规的函数式编程风格的代码,它的特点很显著
- 通过操作符号 “.” 实现了链式操作
- 方法参数传递的是表达式
下面我们分开来说Stream流、Lambda表达式、函数式接口。
2.2.Stream流
Stream流的作用,通过操作符号"."实现函数之间的级联调用,这样有什么好处呢?举一个简单示例。
假如我们需要实现一个表达式的求值:(a+b)*c-d,普通函数调用是这样的
subtract( multiply( add(a,b), c ), d )
函数之间嵌套调用,不利于代码的可读性,换种方式会不会更好呢?Stream流的方式
add(a,b).multiply(c).subtract(d)
通过Stream流的方式,代码清晰明了,可读性非常好!
这就是引入Stream流的价值,它实现了函数之间的级联调用,同时"."操作符号很好理解,对象.方法(参数) ,就是这个套路。
java中的Stream操作,分类为两种
- 中间操作,比如示例代码中的map、filter
- 终止操作,比如示例代码中的min
2.3.Lambda表达式
引入Lambda表达式的作用,简化代码编写。Lambda表达式由三块内容组成
- 输入
- 函数体
- 输出
(a) ->{statement_1;statement_2...; return 输出}
a是输入;statement_1;statement_2...是函数体;return 是输出。
事实上,Lambda表达式是一个语法糖,底层是基于函数接口来实现的,我们把示例代码改造一下,你再看看
// Lambda表达式
Optional<Integer> min = Stream.of("cat", "dog", "pig", "monster", "tiger", "dragLong")
.map(v -> v.length())
.filter(l -> l <= 3)
.min((o1, o2) -> o1 - o2);
// 函数接口(匿名内部类)
Optional<Integer> min2 = Stream.of("cat", "dog", "pig", "monster", "tiger", "dragLong")
.map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return s.length();
}
})
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer i) {
return i <= 3;
}
})
.min(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
如果没有Lambda表达式,通过匿名内部类方式实现函数参数传递,代码非常臃肿!这样看你应该能明白Lambda表达式的价值所在了。
2.4.函数式接口
上面示例代码中,涉及到Function、Predicate、Comparator都是jdk提供的函数式接口。它的作用我们在前面有说过,将函数封装成接口,实现将函数当做参数使用。
那么你知道,函数式接口有什么要求吗?除了jdk提供好的函数式接口,我们能不能自己定义一些函数式接口呢?当然是可以的。
函数式接口的特点
- 函数式接口,需要声明注解@FunctionalInterface
- 函数式接口,只包含一个未实现的方法
比如Function接口
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
比如Predicate接口
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}