函数式编程
函数式编程
- 函数式编程的意义:我们不用从头开始编写大量代码,而是从易于理解、充分测试及可靠的现有小块开始,最后将他们组合在一起创建新的代码
OO(面向对象)是抽象数据,FP(函数式编程)是抽象行为
- 函数式编程中额外的约束:
- 不可变对象:所有数据必须不可变(设置一次,永不改变)
- 将值传递给函数,该函数然后生成新值,但从不修改自身外部的任何东西(包括其参数或其函数范围之外的元素)
- 无副作用:强制如此操作之后,所找到的任何错误都不是所谓副作用引起的
这种范式解决了并发编程中最基本和最棘手的问题:可变共享状态的问题。 可变共享问题:代码的不同部分(在不同的处理器上运行)可以尝试同时修改同一块内存 在函数式编程的约束下,函数永远不会修改现有的值,指挥生成新的值,则不会对内存产生争用。 因此,经常提出纯函数是语言作为并行编程的一个解决方案
- 不可变对象:所有数据必须不可变(设置一次,永不改变)
如果我们希望方法在调用时行为不同应该怎么做? 将代码传递给方法,我们就可以控制它的行为
[[Lambda表达式]]
- Lambda表达式是使用最小可能语法编写的函数定义:
- Lambda表达式产生函数,而不是类
- Lambda语法尽可能少
- 目的是为了使Lambda一遇编写和使用
可以通过Lambda表达式在创建接口对应的对象时,实现接口中的方法
- 目的是为了使Lambda一遇编写和使用
- Lambda表达式的基本语法:
- 参数
- 当只有一个参数,可以不需要括号\
- 为了保持一致性,正常情况使用括号包裹参数
- 如果没有参数,则必须使用空括号
()
表示空参数 - 多个参数必须使用括号
->
- 方法体
- 对于单行的Lambda表达式方法体,禁止使用
return
语句 - 如果要在Lambda表达式中写多行代码,则必须放在花括号中,此时可以使用
return
语句
- 对于单行的Lambda表达式方法体,禁止使用
- 参数
递归
- 递归函数是自我调用的函数
- 当编写递归的Lambda表达式时,需要注意,递归方法必须是实例变量或静态变量,苟泽会出现编译错误
package functional; interface IntCall{ int call(int arg); } public class RecursiveFactorial { static IntCall fact; //静态变量 public static void main(String[] args) { fact = n -> n == 0 ? 1 : n * fact.call(n - 1); for (int i = 0; i < 10; i++) { System.out.println(fact.call(i)); } } } /** * 1 * 1 * 2 * 6 * 24 * 120 * 720 * 5040 * 40320 * 362880 */
package functional;
public class RecursiceFibonacci {
IntCall fib; //实例变量
RecursiceFibonacci(){
fib = n -> n == 0 ? 0 :
n == 1 ? 1:
fib.call(n - 1) + fib.call(n - 2);
}
public static void main(String[] args) {
RecursiceFibonacci rf = new RecursiceFibonacci();
for (int i = 0; i < 10; i++) {
System.out.println(rf.fib.call(i));
}
}
}
/**
* 0
* 1
* 1
* 2
* 3
* 5
* 8
* 13
* 21
* 34
* */
方法引用
- 方法引用组成:
类名或对象名::方法名
Runnable接口
- 它符合单方法接口格式,因此我们可以使用Lambda表达式和方法引用作为
Runnable
:- 方法
run()
不带参数,也没有返回值package functional; class Go{ static void go(){ System.out.println("Go::go"); } } public class RunnableMethodReference { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("Annoymous"); } }).start(); new Thread( ()-> System.out.println("labmda") ).start(); new Thread(Go::go).start(); } } /** * Annoymous * labmda * Go::go */
Thread
对象将Runnable
作为其构造函数参数,并具有会调用run()
的方法 注意:只有匿名内部类才需要具有名为run()
的方法
- 方法
未绑定的方法引用
- 未绑定的方法引用是指没有关联对象的普通(非静态)方法
- 使用未绑定的引用时,我们必须先提供对象
package functional; //一个没有方法引用的对象 class X{ //没有关联对象的普通方法 String f(){ return "X::f()"; } } interface MakeString{ String make(); } interface TransformX{ String transform(X x); } public class UnboundMethodReference { public static void main(String[] args) { // MakeString ms = X::f; //[1] TransformX sp = X::f; X x = new X(); System.out.println(sp.transform(x)); //[2] System.out.println(x.f()); //相同效果 } } /** * X::f() * X::f() */
- 在
[1]
中,报错的原因是:我们在没有X对象的前提下调用f()
- 要解决这个问题,我们必须要有一个
X
对象,如TransformX.transform(X x)
- 要解决这个问题,我们必须要有一个
构造函数的引用
- 可以捕获构造函数的引用,然后通过引用调用该构造函数:
类名::new
[[Lambda表达式#函数式接口|函数式接口]]
package functional;
import java.util.Comparator;
import java.util.function.*;
class AA{}
class BB{}
class CC{}
public class ClassFunctionals {
static AA f1(){return new AA();}
static int f2(AA aa1, AA aa2){return 1;}
static void f3(AA aa){}
static void f4(AA aa, BB bb){}
static CC f5(AA aa){return new CC();}
static CC f6(AA aa, BB bb){return new CC();}
static boolean f7(AA aa){return true;}
static boolean f8(AA aa, BB bb){return true;}
static AA f9(AA aa){return new AA();}
static AA f10(AA aa1, AA aa2){return new AA();}
public static void main(String[] args) {
Supplier<AA> s = ClassFunctionals::f1;
s.get();
Comparator<AA> c = ClassFunctionals::f2;
c.compare(new AA(), new AA());
Consumer<AA> cons = ClassFunctionals::f3;
cons.accept(new AA());
BiConsumer<AA, BB> bicons = ClassFunctionals::f4;
bicons.accept(new AA(), new BB());
Function<AA, CC> f = ClassFunctionals::f5;
CC cc = f.apply(new AA());
BiFunction<AA,BB,CC> bif = ClassFunctionals::f6;
cc = bif.apply(new AA(), new BB());
Predicate<AA> p = ClassFunctionals::f7;
boolean result = p.test(new AA());
BiPredicate<AA,BB> bip = ClassFunctionals::f8;
result = bip.test(new AA(), new BB());
UnaryOperator<AA> uo = ClassFunctionals::f9;
AA aa = uo.apply(new AA());
BinaryOperator<AA> bo = ClassFunctionals::f10;
aa = bo.apply(new AA(), new AA());
}
}
闭包
- 闭包:用于生成函数
考虑一个更复杂的Lambda,它使用函数作用域之外的变量。返回该函数会发生什么>也就是说,当你调用函数时,它对那些外部变量引用了什么?如果语言不能自动解决,那问题将变的非常棘手。能够解决这个问题的语言被称为支持闭包,或者叫做在词法上限定范围。
可以看到,这些函数都共享了package functional; import java.util.function.IntSupplier; class Closure1 { int i; IntSupplier makeFun(int x){ return () -> x + i++; } } public class SharedStorage { public static void main(String[] args) { Closure1 c1 = new Closure1(); IntSupplier i1 = c1.makeFun(0); IntSupplier i2 = c1.makeFun(0); IntSupplier i3 = c1.makeFun(0); System.out.println(i1.getAsInt()); System.out.println(i2.getAsInt()); System.out.println(i3.getAsInt()); } } /** * 0 * 1 * 2 */
i
的存储空间 被lambda表达式引用的局部变量必须是final
或是等同final
效果的等同public Closure6{ IntSupplier makeFun(int x){ int i = 0; x++; i++; final int iFinal = i; final int xFinal = x; return () -> xFinal + iFinal; } }
final
效果:虽然没有明确声明变量是final
的,但是因变量值没被改变过而是基友了final
同等的效果。如果局部变量的初始值永远不会改变,那么它实际上就是final
在lambda表达式中声明的变量,都是final
,因此不能在lambda表达式中修改局部变量的值