在Java的世界里,第一要素是“类”,而在函数式编程里,第一要素则是“函数”。
何为函数式编程
函数式编程是一种编程范式(programming paradigm),也就是如何编写程序的方法论。
它属于“结构化编程”的一种,主要思想是想把运算过程尽量写成一系列嵌套的函数调用。
举例来说,现在有这样一个数学表达式:1
(1+2) * 3 - 4
传统的过程式编写,可能这样写:1
2
3var a = 1 + 2;
var b = a * 3;
var c = b - 4;
函数式编程要求使用函数,可以把这一过程定义为不同的函数,然后写成下面这样:1
var result = subtract(multiply(add(1,2), 3), 4);
这就是函数式编程
特点
函数是第一等公民
函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。1
2var print = function(i){ console.log(i);};
[1,2,3].forEach(print);只用“表达式”,不用“语句”
“表达式”(expression)是一个单纯的运算过程,总是有返回值;
“语句”(statement)是执行某种操作,没有返回值。
函数式编程要求,只使用表达式,不使用语句。
也就是说,每一步都是单纯的运算,而且都有返回值。- 没有“副作用”
“副作用”是指,函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
函数式编程强调没有“副作用”,意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。 - 不修改状态
在其他类型的语言中,变量往往用来保存“状态”(state)。
不修改变量,意味着状态不能保存在变量中。
函数式编程使用参数保存状态,最好的例子就是递归。 - 引用透明
引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或“状态”,
只依赖于输入的参数,任何时候只要参数相同,引用函数所得的返回值总是相同的。
意义
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 更方便的代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。
因此,每一个函数都可以被看做独立单元,很有利于进行单元测试和除错,以及模块化组合。 - 易于“并发”
函数式编程不需要考虑“死锁”,因为它不修改变量,所以根本不存在“锁”线程的问题。1
2
3var s1 = Op1();
var s2 = Op2();
var s3 = concat(s1, s2);
由于s1和s2互不干扰,不会修改变量,谁先执行是无所谓的,所以可以放心地增加线程,把它们分别放在两个线程上完成。
- 代码的热升级
函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。
所以,可以在运行状态下直接升级代码不需要重启。扫盲
函数式编程是一种编程模型,他将计算机运算看做是数学中函数的计算,并且避免了状态以及变量的概念。
在函数式编程中,我们要做的就是把函数传来穿去,而这个,说成术语,我们把他叫做高阶函数。
f(x) = y ,那么这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。
函数式编程的抽象本质则是将函数也作为一个抽象单位,而反映成代码形式,则是高阶函数。
循环是在描述我们该如何地去解决问题。
递归是在描述这个问题的定义。
其实尾递归就是不要保持当前递归函数的状态,而把需要保持的东西全部用参数给传到下一个函数里,这样就可以自动清空本次调用的栈空间。Java8 Lambda表达式
匿名内部类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class CommandTest
{
public static void main(String[] args){
ProcessArray pa = new ProcessArray();
int[] target = {3, -4, 6, 4};
//处理数组,具体处理行为取决于匿名内部类
pa.process(target, new Command(){
public void process(int[] target){
int sum = 0;
for(int tmp: target){
sum += tmp;
}
System.out.println("数组元素的总和:" + sum);
}
});
}
}
Lambda表达式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69public class CommandTest2
{
public static void main(String[] args){
ProcessArray pa = new ProcessArray();
int[] array = [3, -4, 6, 4];
//处理数组,具体处理行为取决于匿名内部类
pa.process(array, (int[] target)->{
int sum = 0;
for(int tmp : target){
sum += tmp;
}
System.out.println("数组元素的总和:" + sum);
});
}
}
Lambda表达式的主要作用就是代替匿名内部类的繁琐语法。
它由三部分组成:
形参列表。形参列表允许省略形参类型。如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
箭头(->)必须通过英文中画线号和大于符号组成。
代码块。如果代码块只包含一条语句,Lambda表达式允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束。
Lambda代码块只有一条return语句,甚至可以省略return关键字。
```java
interface Eatable
{
void taste();
}
interface Flyable
{
void fly(String weather);
}
interface Addable
{
int add(int a, int b);
}
public class LambdaQs{
//调用该方法需要Eatable对象
public void eat(Eatable e){
System.out.println(e);
e.taste();
}
//调用该方法需要Flyable对象
public void drive(Flyable f){
System.out.println("我正在驾驶:" + f);
f.fly("碧空如洗的晴日");
}
//调用该方法需要Addable对象
public void test(Addable add){
System.out.println("5与3的和为: " + add.add(5, 3));
}
public static void main(String[] args){
LambdaQs lq = new LambdaQs();
//Lambda表达式的代码只有一条语句,可以省略花括号
lq.eat(()->System.out.println("苹果的味道不错!"));
//Lambda表达式的形参列表只有一个形参,可以省略圆括号
lq.drive(weather->{
System.out.println("今天天气是:" + weather);
System.out.println("直升飞机飞行平稳");
});
//Lambda表达式的代码块只有一条语句,可以省略花括号
//代码块只有一条语句,即使该表达式需要返回值,也可以省略return关键字
lq.test((a, b)->a + b);
}
}
为了保证Lambda表达式的目标类型是一个明确的函数式接口,可以由如下三种常见的方法:
- 将Lambda表达式赋值给函数式接口类型的变量
- 将Lambda表达式作为函数式接口类型的参数传给某个方法
- 使用函数式接口对Lambda表达式进行强制类型转换