函数式编程
- 使用某种方式操纵其他代码
- 不用从头开始编写大量代码,而是从易于理解、充分测试及可靠的现有小块开始,最后将它们组合在一起以创建新代码。
函数式编程
- 通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。
- OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。
如果希望方法在调用时行为不同,该怎么做呢?
只要能将代码传递给方法,我们就可以控制它的行为。
函数式接口:
- 只有一个方法的接口 ```java interface Strategy { String approach(String msg); }
以其为模板
```java
static String app(String s) {
return s + "?";
}
public static void main(String[] args) {
Strategy[] strategies = new Strategy[]{
new Strategy() {
@Override
public String approach(String msg) {
return "zms"; // 匿名内部类
}
},
msg -> msg + "!", // lambda
TriFunctionTest::app // 方法引用
};
Lambda表达式
方法引用:
方法引用,它以::
为特征。::
的左边是类或对象的名称, ::
的右边是方法的名称,但是没有参数列表。
方法引用组成:类名或对象名,后面跟 ::
,然后跟方法名称。
public class TriFunctionTest {
interface Callable { // [1]
void call(String s);
}
public void func(String s) {
System.out.println("TriFunctionTest.func");
}
public static void funStatic(String s) {
System.out.println("TriFunctionTest.funStatic");
}
public static void main(String[] args) {
Callable callable1 = new TriFunctionTest()::func;
Callable callable2 = TriFunctionTest::funStatic;
callable1.call("zms");
callable2.call("zms");
/**
* TriFunctionTest.func
* TriFunctionTest.funStatic
*/
}
}
- interface和引用的方法的名字不重要,重要的是返回值和参数是不是匹配
- 静态方法引用不需要实例化类对象
- 引用非静态方法需要实例化对象
基本特征
- 参数和函数体被箭头
->
分隔开: msg -> msg + “!” - 当只用一个参数,可以不需要括号
()
如果没有参数,则必须使用括号 () 表示空参数列表。
对于多个参数,将参数列表放在括号 () 中。
- 单行的Lambda不使用return
- 多行的必须使用{}+return
Lambda的例子
public class TriFunctionTest {
interface ClassA {
String A(String a);
}
interface ClassB {
String B(String a, Double b);
}
public static void main(String[] args) {
ClassA classA = str -> str + "!";
System.out.println(classA.A("zms"));
ClassB classB = (str, b) -> str + b + "?";
System.out.println(classB.B("zms", 88.0));
}
}
zms!
zms88.0?
Lambda递归
递归方法必须是成员变量或静态变量,否则会出现编译时错误。
1. 静态变量的情况
public class TriFunctionTest {
private static Fab f;
interface Fab {
int fab(int n);
}
public static void main(String[] args) {
f = n -> n == 0 ? 1 : n == 1 ? 1 : f.fab(n - 1) + f.fab(n - 2);
for (int i = 0; i < 10; i++) {
System.out.println(f.fab(i));
}
}
}
成员变量
public class TriFunctionTest {
Fab f;
interface Fab {
int fab(int n);
}
public static void main(String[] args) {
TriFunctionTest triFunctionTest = new TriFunctionTest();
triFunctionTest.f = n ->
n == 0 ? 1 :
n == 1 ? 1 :
triFunctionTest.f.fab(n - 1) + triFunctionTest.f.fab(n - 2);
for (int i = 0; i < 10; i++) {
System.out.println(triFunctionTest.f.fab(i));
}
}
}
Runnable接口
接口的方法不带参数以及返回值
public abstract void run();
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println("TriFunctionTest.main");
};
runnable.run();
}
例子2:
public static void main(String[] args) {
new Thread(() -> System.out.println("TriFunctionTest.main")).start();
new Thread(() -> {
System.out.println("zms");
System.out.println("vms");
}).start();
// 也可以使用方法引用作为作为参数传入Thread
}
未绑定的方法引用
指没有关联对象的非静态方法.
使用未绑定的引用时,我们必须先提供对象:public class TriFunctionTest {
static class X {
String f() {
return "X::F";
}
}
interface MakeString {
String make();
}
interface TransformX {
String transform(X x);
}
public static void main(String[] args) {
/*
MakeString makeString = X::f; // 不能直接赋值,因为f()不是静态方法
*/
TransformX transformX = X::f;
X x = new X();
System.out.println(transformX.transform(x));
}
}
引用f()时候需要使用this对象,因为f()不是静态的方法,必须有一个对象对他进行调用.
拿到未绑定的方法引用,并且调用它的transform()
方法,将一个X类的对象传递给它,最后使得x.f()
以某种方式被调用。Java知道它必须拿第一个参数,该参数实际就是this
对象,然后对此调用方法。
如果你的方法有更多个参数,就以第一个参数接受this的模式来处理。
public class TriFunctionTest {
static class X {
String f(int a) {
return "X::F" + a;
}
}
interface MakeString {
String make();
}
interface TransformX {
String transform(X x, int a);// 自动将a传给了x对象
}
public static void main(String[] args) {
/*
MakeString makeString = X::f; // 不能直接赋值,因为f()不是静态方法
*/
TransformX transformX = X::f;
X x = new X();
System.out.println(transformX.transform(x,1)); // X::F1
}
}
构造函数引用
构造函数只有一个相同名称:
:: new,根据不同的参数确定具体的构造函数!
```java class Dog { String name; int age = -1; // For “unknown” Dog() { name = “stray”; } Dog(String nm) { name = nm; } Dog(String nm, int yrs) { name = nm; age = yrs; } }
public class TriFunctionTest { interface MakeNoArgs { Dog make(); }
interface Make1Arg {
Dog make(String nm);
}
interface Make2Args {
Dog make(String nm, int age);
}
public static void main(String[] args) {
MakeNoArgs noArgs = Dog::new;
Make1Arg make1Arg = Dog::new;
Make2Args make2Args = Dog::new;
System.out.println(noArgs.make());
System.out.println(make1Arg.make("zms"));
System.out.println(make2Args.make("zms", 11));
// fp.Dog@6d03e736 // fp.Dog@568db2f2 // fp.Dog@378bf509 } }
<a name="ZAz7O"></a>
## 函数式接口
- 方法引用和 Lambda 表达式都必须被赋值,同时赋值需要类型信息才能使编译器保证类型的正确性。
- Lambda 表达式包含 类型推导 (编译器会自动推导出类型信息,避免了程序员显式地声明)。编译器必须能够以某种方式推导出 x 的类型。
- 当 Lambda 表达式被赋值时,编译器必须确定 x 和 y 的确切类型以生成正确的代码。
(x, y) -> x + y
- 使用 `@`**`FunctionalInterface` **注解强制执行此“函数式方法”模式:
- 每个接口只包含**一个抽象方法**,称为 _函数式方法_ 。
```java
// functional/FunctionalAnnotation.java
@FunctionalInterface
interface Functional {
String goodbye(String arg);
}
interface FunctionalNoAnn {
String goodbye(String arg);
}
/*
@FunctionalInterface
interface NotFunctional {
String goodbye(String arg);
String hello(String arg);
}
产生错误信息:
函数式方法必须只有一个抽象函数
*/
public class FunctionalAnnotation {
public String goodbye(String arg) {
return "Goodbye, " + arg;
}
public static void main(String[] args) {
FunctionalAnnotation fa =
new FunctionalAnnotation();
Functional f = fa::goodbye;
FunctionalNoAnn fna = fa::goodbye;
// Functional fac = fa; //错误!FunctionalAnnotation 并没有显式地去实现 Functional 接口。
Functional fl = a -> "Goodbye, " + a;
FunctionalNoAnn fnal = a -> "Goodbye, " + a;
}
}
自带的函数接口
命名规则
- 处理的是对象而不是基本类型
Function
Consumer
Predicate
- 接受的参数是基本类型,由名称的第一部分进行标识
LongConsumer
DoubleFunction
IntPredicate
- 返回基本类型的Supplier(无参数返回任意类型)除外
- 如果返回值是基本类型,用
To
表示ToLongFunction<T>
IntToLongFunction
- 返回类型和参数类型相同
Operator
UnaryOPerator
单个参数BinaryOperator
两个参数
- 接受参数并返回boolean使用谓词
Predicate
- 接受的两个参数类型不同,名称中有一个
Bi
系统接口
特征 | 函数式方法名 | 示例 |
---|---|---|
无参数; 无返回值 |
Runnable (java.lang) run() |
Runnable |
无参数; 返回类型任意 |
Supplierget() getAs类型() |
Supplier<T> BooleanSupplier IntSupplier LongSupplier DoubleSupplier |
无参数; 返回类型任意 |
Callable (java.util.concurrent) call() |
Callable<V> |
1 参数; 无返回值 |
Consumeraccept() |
Consumer<T> IntConsumer LongConsumer DoubleConsumer |
2 参数 Consumer | BiConsumeraccept() |
BiConsumer<T,U> |
2 参数 Consumer; 第一个参数是 引用; 第二个参数是 基本类型 |
Obj类型Consumeraccept() |
ObjIntConsumer<T> ObjLongConsumer<T> ObjDoubleConsumer<T> |
1 参数; 返回类型不同 |
Functionapply() To类型 和 类型To类型 applyAs类型() |
Function<T,R> IntFunction <R> LongFunction<R> DoubleFunction <R> ToIntFunction <T> ToLongFunction<T> ToDoubleFunction<T> IntToLongFunction IntToDoubleFunction LongToIntFunction LongToDoubleFunction DoubleToIntFunction DoubleToLongFunction |
1 参数; 返回类型相同 |
UnaryOperatorapply() |
UnaryOperator<T> IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
2 参数,类型相同; 返回类型相同 |
BinaryOperatorapply() |
BinaryOperator<T> IntBinaryOperator LongBinaryOperator DoubleBinaryOperator |
2 参数,类型相同; 返回整型 |
Comparator (java.util) compare() |
Comparator<T> |
2 参数; 返回布尔型 |
Predicatetest() |
Predicate<T> BiPredicate<T,U> IntPredicate LongPredicate DoublePredicate |
参数基本类型; 返回基本类型 |
类型To类型FunctionapplyAs类型() |
IntToLongFunction IntToDoubleFunction LongToIntFunction LongToDoubleFunction DoubleToIntFunction DoubleToLongFunction |
2 参数; 类型不同 |
Bi操作 (不同方法名) |
BiFunction<T,U,R> BiConsumer<T,U> BiPredicate<T,U> ToIntBiFunction<T,U> ToLongBiFunction<T,U> ToDoubleBiFunction<T> |
例子:
所有不同的Function变体实例
// functional/FunctionVariants.java
import java.util.function.*;
class Foo {}
class Bar {
Foo f;
Bar(Foo f) { this.f = f; }
}
class IBaz {
int i;
IBaz(int i) {
this.i = i;
}
}
class LBaz {
long l;
LBaz(long l) {
this.l = l;
}
}
class DBaz {
double d;
DBaz(double d) {
this.d = d;
}
}
public class FunctionVariants {
static Function<Foo,Bar> f1 = f -> new Bar(f);
static IntFunction<IBaz> f2 = i -> new IBaz(i);
static LongFunction<LBaz> f3 = l -> new LBaz(l);
static DoubleFunction<DBaz> f4 = d -> new DBaz(d);
static ToIntFunction<IBaz> f5 = ib -> ib.i;
static ToLongFunction<LBaz> f6 = lb -> lb.l;
static ToDoubleFunction<DBaz> f7 = db -> db.d;
static IntToLongFunction f8 = i -> i;
static IntToDoubleFunction f9 = i -> i;
static LongToIntFunction f10 = l -> (int)l;
static LongToDoubleFunction f11 = l -> l;
static DoubleToIntFunction f12 = d -> (int)d;
static DoubleToLongFunction f13 = d -> (long)d;
public static void main(String[] args) {
Bar b = f1.apply(new Foo());
IBaz ib = f2.apply(11);
LBaz lb = f3.apply(11);
DBaz db = f4.apply(11);
int i = f5.applyAsInt(ib);
long l = f6.applyAsLong(lb);
double d = f7.applyAsDouble(db);
l = f8.applyAsLong(12);
d = f9.applyAsDouble(12);
i = f10.applyAsInt(12);
d = f11.applyAsDouble(12);
i = f12.applyAsInt(13.0);
l = f13.applyAsLong(13.0);
}
}
这些 Lambda 表达式尝试生成适合函数签名的最简代码。 在某些情况下有必要进行强制类型转换,否则编译器会报截断错误。
使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法名,例Runnable的run()
,而不是你的方法名。
// functional/ClassFunctionals.java
import java.util.*;
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());
}
}
每个方法名称都是随意的(如 f1()
,f2()
等)。正如你刚才看到的,一旦将方法引用赋值给函数接口,我们就可以调用与该接口关联的函数方法。 在此示例中为 get()
、compare()
、accept()
、apply()
和 test()
。
自定义接口
接口是有限的。比如有 BiFunction
,但也仅此而已。 如果需要三参数函数的接口怎么办? 其实这些接口非常简单,很容易查看 Java 库源代码并自行创建。
@FunctionalInterface
public interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
// functional/TriFunctionTest.java
public class TriFunctionTest {
static int f(int i, long l, double d) { return 99; }
public static void main(String[] args) {
TriFunction<Integer, Long, Double, Integer> tf =
TriFunctionTest::f; // 使用引用进行赋值
tf = (i, l, d) -> 12; // 使用lambda进行赋值
}
}
高阶函数
- 消费或者产生函数的函数
产生函数
public class ProduceFunction {
interface FuncSS extends Function<String, String> {
}
static FuncSS produce() {
//返回一个FuncSS,所以下面的s可以知道是String
return s -> s.toLowerCase();
}
public static void main(String[] args) {
FuncSS funcSS = produce();
System.out.println(funcSS.apply("VMS"));
}
}
produce()
是高阶函数。
class Two { }
public class ProduceFunction {
static Two consume(Function
public static void main(String[] args) {
System.out.println(consume(one -> new Two()));
}
}
基于消费函数生成新函数:
```java
class I {
@Override
public String toString() {
return "I";
}
}
class O {
@Override
public String toString() {
return "O";
}
}
public class ProduceFunction {
static Function<I, O> transform(Function<I, O> in) {
return in.andThen(o -> {
System.out.println(o);
return o;
});
}
public static void main(String[] args) {
Function<I, O> f = transform(i -> {
System.out.println(i);
return new O();
});
O o = f.apply(new I());
}
}
使用到了 Function
接口中名为 andThen()
的默认方法,该方法专门用于操作函数。
在调用 in
函数之后调用 andThen().
要附加一个 andThen()
函数,我们只需将该函数作为参数传递。 transform()
产生的是一个新函数,它将 in
的动作与 andThen()
参数的动作结合起来。
闭包
如果一个lambda使用了作用域之外的变量,返回该函数会发生什么?
Lambda 可以没有限制地引用 实例变量和静态变量。但 局部变量必须显式声明为final,或事实上是final 。
引用外部变量
// functional/Closure1.java
import java.util.function.*;
public class Closure1 {
int i; // 对于函数来说是一个外部变量
IntSupplier makeFun(int x) {
return () -> x + i++;
}
}
如果你对同一个对象多次调用 makeFun()
,你最终会得到多个函数,它们共享 i
的存储空间:
class Closure1 {
int i;
IntSupplier makeFun(int x) {
return () -> x + i++;
}
}
public class ProduceFunction {
public static void main(String[] args) {
Closure1 closure1 = new Closure1();
IntSupplier f1 = closure1.makeFun(0);
IntSupplier f2 = closure1.makeFun(0);
IntSupplier f3 = closure1.makeFun(0);
System.out.println(f1.getAsInt());
System.out.println(f2.getAsInt());
System.out.println(f3.getAsInt());
/*
0
1
2
*/
}
}
每次调用 getAsInt()
都会增加 i
,表明存储是共享的。
引用局部变量
class Closure1 {
IntSupplier makeFun(int x) {
int i = 0;
return () -> x + i;
}
}
makeFun()
返回的 IntSupplier
“关住了” i
和 x
,因此即使makeFun()
已执行完毕,当你调用返回的函数时i
和 x
仍然有效,而不是像正常情况下那样在 makeFun()
执行后 i
和x
就消失了。
- 编译不通过
x
和 i
的操作都犯了同样的错误:
被 Lambda 表达式引用的局部变量必须是 final 或者是等同 final (变量值没被改变过而实际有了 final
同等的效果)效果的。
- 编译不通过
改变一下:
- 但与对象类型,只要对应的内存地址不改变就可以 ```java // functional/Closure8.java
import java.util.; import java.util.function.;
public class Closure8 {
Supplier> makeFun() {
final List
[1] [1] [1, 42] [1, 96]
下面是错误的情况
public class Closure9 {
Supplier> makeFun() {
List
- 内部类的情况
![image.png](https://cdn.nlark.com/yuque/0/2021/png/8429887/1611152381753-ed18c063-e06b-43fa-98f3-f7ae78a734c3.png#align=left&display=inline&height=317&margin=%5Bobject%20Object%5D&name=image.png&originHeight=317&originWidth=449&size=20380&status=done&style=none&width=449)<br />也是不可以的!
<a name="dXfRr"></a>
## 函数组合
- 多个函数组合成新的函数
| 组合方法 | 支持接口 |
| --- | --- |
| `andThen(argument)`<br />执行原操作,再执行参数操作 | **Function<br />BiFunction<br />Consumer<br />BiConsumer<br />IntConsumer<br />LongConsumer<br />DoubleConsumer<br />UnaryOperator<br />IntUnaryOperator<br />LongUnaryOperator<br />DoubleUnaryOperator<br />BinaryOperator** |
| `compose(argument)`<br />执行参数操作,再执行原操作 | **Function<br />UnaryOperator<br />IntUnaryOperator<br />LongUnaryOperator<br />DoubleUnaryOperator** |
| `and(argument)`<br />原谓词(Predicate)和参数谓词的短路**逻辑与** | **Predicate<br />BiPredicate<br />IntPredicate<br />LongPredicate<br />DoublePredicate** |
| `or(argument)`<br />原谓词和参数谓词的短路**逻辑或** | **Predicate<br />BiPredicate<br />IntPredicate<br />LongPredicate<br />DoublePredicate** |
| `negate()`<br />该谓词的**逻辑非**<br />**<br /> p1.negate() 表示对P1的结果取非 | **Predicate<br />BiPredicate<br />IntPredicate<br />LongPredicate<br />DoublePredicate** |
- Function版本
```java
public class ProduceFunction {
static Function<String, String>
f1 = s -> {
System.out.println(s);
return s.replace('A', '_');
},
f2 = s -> s.substring(3),
f3 = s -> s.toLowerCase(),
f4 = f1.compose(f2).andThen(f3); //f2->f1->f3
public static void main(String[] args) {
System.out.println(f4.apply("GO AFTER ALL AMBULANCES"));
}
/*
AFTER ALL AMBULANCES
_fter _ll _mbul_nces
*/
}
当 f1
获得字符串时,它已经被f2
剥离了前三个字符。这是因为 compose(f2)
表示 f2
的调用发生在 f1
之前。
- 谓词Predicate版本 ```java // functional/PredicateComposition.java
import java.util.function.; import java.util.stream.;
public class PredicateComposition {
static Predicate
=== foobar foobaz
`p4` 获取到了所有谓词(`Predicate`)并组合成一个更复杂的谓词。
- 如果字符串中不包含 `bar` 且长度小于 5,或者它包含 `foo` ,则结果为 `true`。
<a name="zrnXL"></a>
## 柯里化
将一个多参数的函数,转换为一系列单参数函数。
```java
// functional/CurryingAndPartials.java
import java.util.function.*;
public class CurryingAndPartials {
// 未柯里化:
static String uncurried(String a, String b) {
return a + b;
}
public static void main(String[] args) {
// 柯里化的函数:
Function<String, Function<String, String>> sum =
a -> b -> a + b; // [1]
System.out.println(uncurried("Hi ", "Ho"));
Function<String, String>
hi = sum.apply("Hi "); // [2]
System.out.println(hi.apply("Ho"));
// 部分应用:
Function<String, String> sumHi =
sum.apply("Hup ");
System.out.println(sumHi.apply("Ho"));
System.out.println(sumHi.apply("Hey"));
}
}
===
Hi Ho
Hi Ho
Hup Ho
Hup Hey
[1] 这一连串的箭头很巧妙。注意,在函数接口声明中,第二个参数是另一个函数。
[2] 柯里化的目的是能够通过提供单个参数来创建一个新函数,所以现在有了一个“带参函数”和剩下的 “自由函数”(free argument) 。
每一级的箭头级联(Arrow-cascading),你都会在类型声明周围包裹另一个 Function 。
三级
// functional/Curry3Args.java
import java.util.function.*;
public class Curry3Args {
public static void main(String[] args) {
Function<String,
Function<String,
Function<String, String>>> sum =
a -> b -> c -> a + b + c;
Function<String,
Function<String, String>> hi =
sum.apply("Hi ");
Function<String, String> ho =
hi.apply("Ho ");
System.out.println(ho.apply("Hup")); ///Hi Ho Hup
}
}
处理基本类型和装箱时,请使用适当的函数式接口:
// functional/CurriedIntAdd.java
import java.util.function.*;
public class CurriedIntAdd {
public static void main(String[] args) {
IntFunction<IntUnaryOperator>
curriedIntAdd = a -> b -> a + b;
IntUnaryOperator add4 = curriedIntAdd.apply(4);
System.out.println(add4.applyAsInt(5)); ///9
}
}