什么是函数式编程
函数式编程是一种声明式的编程范式,起源于学术界,从lambda演算演变而来,最初的函数式语言是Lisp。
函数完成的事从输入value到输出value到映射,f(x)=x2+1而不是像在指令式编程中int i = 0; i++ 那样直接更新程序的状态
常见的函数式语言有Lisp,ML,Scala,Javascript
lambda演算上一种仅基于函数的形式计算系统,全部的组成元素都是函数,数字是函数,计算是函数,分支是函数,循环也是使用函数递归
语法 | 名称 | 描述 |
---|---|---|
x | 标识符 | 标识参数或者数学上的值或者表示逻辑上的值 |
(λx.M) | 抽象化 | 一个完整的函数定义(M是一个lambda项),在表达式中的x都会绑定为变量x |
(MN) | 应用 | 将函数M作用于函数N |
例如,f(x)=x+2可以用lambda演算表示为λx.x + 2,而f(3)的值可以写做(λx.x+2) 3
lambda表达式
lambda参数 | 说明 | 实例 |
---|---|---|
(类型 标识符,类型 标识符, ……) | 最普通的情况 | (String x,String y) |
(标识符,标识符, ……) | 当参数的类型可推断时,可以省略参数类型 | (x,y) |
标识符 | 当参数的类型可推断且还只有一个时,类别和括号可以省略 | x |
lambda主体 | 实例 |
---|---|
表达式 | x.toLowerCase() + “^” + y.toLowerCase() |
{语句;} | { return x.toLowerCase() + “^” + y.toLowerCase();} |
例如: |
(String x, String y) -> x.toLowerCase() + "^" + y.toLowerCase();
(String x, String y) -> { return x.toLowerCase() + "^" + y.toLowerCase();};
(x, y) -> x.toLowerCase() + "^" + y.toLowerCase();
x -> x.toLowerCase() ;
函数是一等公民
可以通过标识符来引⽤访问,凡是可以使⽤value的地⽅都可以使⽤函数,例如传参,返回结果、对象的域、计算等等
高阶函数
输⼊存在函数或返回函数的函数称为⾼阶函数。这允许我们使⽤组合和声明的⽅式来写代码,程序由很多更⼩的函数组合⽽成。程序本身更接近于数学的定义
纯函数
纯函数,当有⼀个相同的输⼊,必定有相同的输出,没有任何副作⽤
- 重新赋值变量; Object x = new Object();…;x = new Object();
- 就地修改⼀个数据结构; list.add(10);
- 设置实例的域; person.age = 10;…;person.age = 12;
- 抛出异常或者出现error;// 引⽤透明, Referentially Transparent
- 打印到控制台或者读取⽤户的输⼊,读写⽂件,在屏幕上绘制
对单元测试⾮常友好;天然线程安全,容易实现并发;容易实现逻辑推导;更⾼的抽象层
次,⽅便模块重⽤等
数据结构根本区分不出来是不是同⼀个对象,也不需要区分,因为不可变
在函数式编程语⾔中,例如ML语⾔,根本不需要考虑引⽤和值的区别
⽆需关注数据的变化,只需关注过程就可以了,因为数据是不变的
递归与尾递归
如果⼀个函数中所有递归形式的调⽤都出现在函数的末尾,我们称这个递归函数是尾递归
的,⼤多数编译器会利⽤这种特点⾃动⽣成优化的代码
闭包
函数中的⾮局部变量称为⾃由变量,闭包就是函数和函数的运⾏环境
运⾏环境有两种:
Lexical scope,⾃由变量使⽤的是函数定义时的值
Dynamic Scope,⾃由变量使⽤的是函数调⽤时的值
Java使⽤的是Lexical scope。
Currying 柯⾥化
Currying来实现函数的多参数,是把接受多个参数的函数变成单⼀参数(最初函数的第⼀个
参数)的函数,并且返回接受余下的参数⽽且返回结果的新函数。
(x, y) -> x + y;
x -> y -> x+ y;
(x, y, z) -> x + y + z;
x -> (y -> (z -> x + y + z))
lambda表达式
常见形式
(String x, String y) -> x.toLowerCase() + "^" + y.toLowerCase()
(String x, String y) -> { return x.toLowerCase() + "^" + y.toLowerCase();}
(x, y) -> { return x.toLowerCase() + "^" + y.toLowerCase();}
x -> x.toLowerCase()
x -> y -> x.toLowerCase() + "^" + y.toLowerCase()
类型
同样的Lambda表达式可以满⾜不同的参数需求,例如Callable和Supplier
Callable<Integer> c = () -> 4;
Supplier<Integer> s = () -> 4;
BiFunction<Integer, Integer, Integer> f2 = (Integer x, Integer y) -> x - y;
Comparator<Integer> comparator = (Integer x, Integer y) -> x - y;
Lambda表达式并不同于Java的其他对象依赖于对象的Type,Callable和Supplier,BiFunction
和Comparator的实质都是⼀样的,关注的是⽅法签名
函数式接口
函数式接⼝就是只定义⼀个抽象⽅法的接⼝
⼀般会使⽤@FunctionalInterface注解进⾏辅助检查
@FunctionalInterface必须使⽤在接⼝上,且接⼝中只能存在⼀个抽象⽅法;若抽象⽅法的签名和Object中的签名是相同的,且该抽象⽅法不计数,例如Comparator
只要Lambda表达式的函数签名和函数式接⼝中的抽象⽅法签名可以兼容,则该Lambda表达式就可以使⽤该Type表示
常⻅的函数式接⼝有Predicate,Consumer,Supplier,Function,BiFunction
public interface Predicate<T> {
boolean test(T t);
}
public interface Consumer<T> {
void accept(T t);
}
public interface Supplier<T> {
T get();
}
public interface Function<T, R> {
R apply(T t);
}
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
方法引用
⽅法引⽤可以重复使⽤现有的⽅法定义,并像Lambda⼀样传递它们
-
指向静态方法,Integer::parseInt
-
指向类型的实例方法,String::length
-
指向现存对象或表达式的实例方法System,out::println
第三种⽅法引⽤主要⽤在你需要在Lambda中调⽤⼀个现存的外部对象时使⽤
自由变量
Lambda表达式也允许使⽤⾃由变量,⾃由变量使⽤的是Lexcial Scope中的值
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 23 // ERROR