以前写过一篇java8的流操作,人们都说流操作是函数式编程,但函数式编程是什么呢?
什么是函数式编程
什么是函数式编程?它是一种编程范式,即一切都是数学函数。函数式编程语言里也可以有对象,但通常这些对象都是恒定不变的 —— 要么是函数参数,要什么是函数返回值。函数式编程语言里没有 for/next 循环,因为这些逻辑意味着有状态的改变。相替代的是,这种循环逻辑在函数式编程语言里是通过递归、把函数当成参数传递的方式实现的。
函数式编程单看上面的理论无疑是无法理解的,所以,需要通过它的一些特点来感受什么是函数式编程
函数是"第一等公民"
所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。 举例来说,下面代码中的print变量就是一个函数,可以作为另一个函数的参数。
只用"表达式",不用"语句"
"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操作,所以就被排斥在外。
当然,实际应用中,不做I/O是不可能的。因此,编程过程中,函数式编程只要求把I/O限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。
没有"副作用"
所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值.
不修改状态
上一点已经提到,函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。
在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。
引用透明
引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。
在java中,函数式编程是通过lambda表达式实现的。
lambada表达式
基本表达形式如下:
Runnable noArguments = () -> System.out.println("Hello World");
Lambda 表达式的基本语法是:
参数。(可以有多个)
接着 ->,可视为“产出”。
-> 之后的内容都是方法体。
当只用一个参数,可以不需要括号()。 然而,这是一个特例。
正常情况使用括号()包裹参数。 为了保持一致性,也可以使用括号()包裹单个参数,虽然这种情况并不常见。
如果没有参数,则必须使用括号()表示空参数列表。
对于多个参数,将参数列表放在括号()中。
方法体的语句超过一句时,需要使用{},并根据情况看是否需要return
Lambda 表达式产生函数,而不是类。 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 Lambda 看起来像函数 —— 但作为程序员,你可以高兴地假装它们“只是函数”。
函数接口
/** 强制 javac 检查一个接口是否符合函数接口的标准。如果该注释添加给一个枚举类型、类或另一个注释,或者接口包含不止一个抽象方法,javac 就会报错 **/
@FunctionalInterface //该注解不是必选项
interface Yunzhi {
//只包含一个抽象方法,称为函数式方法。
String test(String s);
}
public class LambdaExpressions {
static Yunzhi yunzhi = x ->"this is a" + x ;
public static void main(String[] args) {
System.out.println(yunzhi.test("test"));
}
}
yunzhi实际上是实现了Yunzhi的test(),类似上面的这种接口叫函数式接口。
JDK 8 中提供了大量的函数接口,这些接口定义在java.util.function中,因此我们一般情况下不需再定义自己的接口,同时,各个接口的作用和名字都是相对应的,所以,了解函数式接口的命名模式就是很有必要的了。
以下是基本命名准则:
如果只处理对象而非基本类型,名称则为Function,Consumer,Predicate等。参数类型通过泛型添加。
如果接收的参数是基本类型,则由名称的第一部分表示,如LongConsumer,DoubleFunction,IntPredicate等,但基本Supplier类型例外。
如果返回值为基本类型,则用To表示,如ToLongFunction 和IntToLongFunction。
如果返回值类型与参数类型一致,则是一个运算符:单个参数使用UnaryOperator,两个参数使用BinaryOperator。
如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。
如果接收的两个参数类型不同,则名称中有一个Bi。
下表描述了 java.util.function 中的目标类型(包括例外情况):
特征
数式方法名
示例
无参数;
无返回值
Runnable
(java.lang)
run()
Runnable
无参数;
返回类型任意
Supplier
get()
getAs类型()
Supplier
BooleanSupplier
IntSupplier
LongSupplier
DoubleSupplier
无参数;
返回类型任意
Callable
(java.util.concurrent)
call()
Callable
1 参数;
无返回值
Consumer
accept()
Consumer
IntConsumer
LongConsumer
DoubleConsumer
2 参数 Consumer
BiConsumer
accept()
BiConsumer
2 参数 Consumer;
1 引用;
1 基本类型
Obj类型Consumer
accept()
ObjIntConsumer
ObjLongConsumer
ObjDoubleConsumer
1 参数;
返回类型不同
Function
apply()
To类型 和 类型To类型
applyAs类型()
Function
IntFunction
LongFunction
DoubleFunction
ToIntFunction
ToLongFunction
ToDoubleFunction
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction
1 参数;
返回类型相同
UnaryOperator
apply()
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
2 参数类型相同;
返回类型相同
BinaryOperator
apply()
BinaryOperator
IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator
2 参数类型相同;
返回整型
Comparator
(java.util)
compare()
Comparator
2 参数;
返回布尔型
Predicate
test()
Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate
参数基本类型;
返回基本类型
类型To类型Function
applyAs类型()
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction
2 参数类型不同
Bi操作
(不同方法名)
BiFunction
BiConsumer
BiPredicate
ToIntBiFunction
ToLongBiFunction
ToDoubleBiFunction
其中最常用的是:
接下来实际的看看如何使用这些接口
class Foo {}
class Bar {
Foo f;
Bar(Foo f) { this.f = f; }
}
public class FunctionVariants {
static Function f1 = f -> new Bar(f);
public static void main(String[] args) {
Bar b = f1.apply(new Foo());
}
}
学到这里才突然发现,虽然以前完全不知道函数接口这个东西,但实际上却已经在使用他了,比如Comparator广泛的运用在各种比较当中,pridect也在spring的综合查询中使用过了。
关于函数式编程的争议
虽然在宣传中,函数式编程有着巨大的优势,比如适合并行编程、代码可靠性和代码创建和库复用,但在某些大佬看来:
关于函数式编程能高效创建更健壮的代码这一观点仍存在部分争议。虽然已有一些好的范例,但还不足以证明纯函数式语言就是解决编程问题的最佳方法。
不管怎样,多学一学总是好的
参考文章