这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
函数式编程(functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算为该语言最重要的基础。而且,λ演算的函数可以接受函数作为输入参数和输出返回值。
比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
1. 概念
1.1 一等公民 First-class_citizen
- 在函数式编程中,函数被视为一等公民,这意味着它们可以绑定到名称(包括本地标识符),作为参数传递,并从其他函数返回,就像任何其他数据类型一样。这允许以声明性和可组合的风格编写程序,其中小功能以模块化方式组合
1.2 一等函数 First-class_function
- 在具有一等函数的语言中,函数的名称没有任何特殊的地位;它们被视为普通变量带有函数类型。这个词是克里斯托弗·斯特雷奇在 1960 年代中期在“functions as first-class citizens”(函数作为一等公民)的背景下创造的。
- 一等函数是函数式编程风格的必要条件,其中使用高阶函数是一种标准做法。
- 高阶函数与一等函数密切相关,因为高阶函数和一等函数都允许函数作为其他函数的参数和结果。两者之间的区别很微妙:“高阶”描述了对其他函数进行运算的函数的数学概念,而“一等”是编程语言实体的计算机科学术语,它们的使用没有限制(因此,一级函数可以出现在程序中任何其他一级实体(如数字)可以出现的地方,包括作为其他函数的参数和返回值)。
1.3 # 高阶函数 Higher-order_function
-
在数学和计算机科学中,高阶函数是至少执行以下一项操作的函数:
- 将一个或多个函数作为参数(即过程参数)
- 返回一个函数作为其结果。
-
高阶函数twice采用一个函数,并将该函数应用于某个值两次。如果twice必须多次应用这个函数,它最好应该返回一个函数而不是一个值。这符合“Don't repeat yourself”(不要重复自己)的原则。
import java.util.function.*;
class Main {
public static void main(String[] args) {
Function<IntUnaryOperator, IntUnaryOperator> twice = f -> f.andThen(f);
IntUnaryOperator plusThree = i -> i + 3;
var g = twice.apply(plusThree);
System.out.println(g.applyAsInt(7)); // 13
}
}
1.4 纯函数 Pure_function
纯函数没有副作用(内存或 I/O)。这意味着纯函数有几个有用的属性,其中许多可用于优化代码。
- 如果不使用纯表达式的结果,则可以将其删除而不影响其他表达式。
- 如果使用不会引起副作用的参数调用纯函数,则结果相对于该参数列表是常量(有时称为引用透明性或幂等性),即,再次使用相同参数调用纯函数会返回相同的结果。
- 如果两个纯表达式之间没有数据依赖,那么它们的顺序可以颠倒,或者它们可以并行执行并且它们不会相互干扰(换句话说,任何纯表达式的计算都是线程安全的)。
- 如果整个语言不允许副作用,那么可以使用任何评估策略;这使编译器可以自由地重新排序或组合程序中表达式的计算。
2. 回到java来,随便谈谈
λ表达式的类型是什么?某些语言使用函数值或函数对象来表示 λ表达式,但不适用于 Java 语言。
相反,Java 使用函数式接口来表示 lambda 表达式类型。乍一看似乎很奇怪,但实际上这是确保与旧版本 Java 向后兼容的有效方法。
2.1 指令式转变到函数式
下面这段代码应该很熟悉:
Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println("In another thread");
}
});
thread.start();
System.out.println("In main");
本Thread类和它的构造在Java 1.0中引入,远远超过20年前。从那以后,构造函数没有改变。将匿名实例传递Runnable给构造函数是传统的做法。但是从 Java 8 开始,您可以选择传递 lambda 表达式:
Thread thread = new Thread(() ‑> System.out.println("In another thread"));
Thread类的构造函数需要一个实现Runnable。 在这种情况下,我们传递的是一个 lambda 表达式,而不是传递一个对象。我们可以选择使用各种方法和构造函数传递 lambda 表达式,包括一些在 Java 8 之前创建的。它之所以有效,是因为 lambda 表达式在 Java 中表示为函数式接口。
2.2 Java的函数式接口有三个重要规则:
- 一个功能接口只有一个抽象方法。
- 任何也是
Object类中公共方法的抽象方法都不算作该方法。 - 功能接口可能有默认方法和静态方法。
任何满足这个单一抽象方法规则的接口都将被自动视为函数式接口。这包括传统接口,例如Runnable和Callable。
2.3 小结
lambda 表达式成为一种函数式接口的设计决策促进了 Java 8 和 Java 早期版本之间的向后兼容性。
可以将 lambda 表达式传递给任何通常接收单个抽象方法接口的旧函数。
要接收 lambda 表达式,方法的参数类型应该是函数式接口。