函数式编程
函数式编程(Functional Programming)
- OO(object oriented,面向对象)是抽象数据
- FP(Functional programming,函数式编程)是抽象行为
约束
函数式编程是安全的,它增加了一些约束,即所有的数据(变量、对象)必须是不可变的,设置一次,永不改变。不修改自身外部的任何东西(函数范围之外的元素)
优点
不可变对象范式解决了并发编程中棘手问题(当程序的某些部分在多处理器同时运行时),意味着不同的处理器可以尝试同时修改同一块内存。
如果函数永远不会修改现有值而是产生新值,则不会对内存产生争用,这是函数式语言天生的优点。
函数式语言作为并行编程的其中之一的解决方案。
Lambda 表达式
Lambda 表达式使用最小可能的语法编写的函数定义
- Lambda 表达式产生函数,而不是类。
- Lambda 语法尽可能少,这使 Lambda 易于编写和使用
interface Description {
String brief();
}
interface Body {
String detailed(String head);
}
interface Multi {
String twoArg(String head, Double d);
}
public class LambdaExpressions {
static Body bod = h -> h + " No Parens!"; // [1]
static Body bod2 = (h) -> h + " More details"; // [2]
static Description desc = () -> "Short info"; // [3]
static Multi mult = (h, n) -> h + n; // [4]
static Description moreLines = () -> { // [5]
System.out.println("moreLines()");
return "from moreLines()";
};
public static void main(String[] args) {
System.out.println(bod.detailed("Oh!"));
System.out.println(bod2.detailed("Hi!"));
System.out.println(desc.brief());
System.out.println(mult.twoArg("Pi! ", 3.14159));
System.out.println(moreLines.brief());
}
}
output:
Oh! No Parens!
Hi! More details
Short info
Pi! 3.14159
moreLines()
from moreLines()
语法
a -> c
a :参数
-> : 表示为产出
c : 方法体
上例中展示了 Lambda 的几种使用方法
[1] 当使用一个参数时,可以不需要括号()
[2] 使用括号 () 包裹参数
[3] 如果没有参数则必须使用括号 () 表示空参数列表
[4] 当使用多个参数,将参数列表包裹在括号 () 中
[5] 如果方法体一行写不完,需要使用花括号 {} 将方法体包裹其中,这种情况下需要使用return 来返回
方法引用
方法引用组成:类名或对象名 :: 方法名称
interface Callable { // [1]
void call(String s);
}
class Describe {
// show() 的签名(参数和返回类型)符合 Callable 的 call()签名
void show(String msg) { // [2]
System.out.println(msg);
}
}
public class MethodReferences {
// hello() 符合call() 签名
static void hello(String name) { // [3]
System.out.println("Hello, " + name);
}
static class Description {
String about;
Description(String desc) {
about = desc;
}
// help() 符合call() 签名,它是静态内部类中的非静态方法
void help(String msg) { // [4]
System.out.println(about + " " + msg);
}
}
static class Helper {
// assist() 是静态内部类中的静态方法
static void assist(String msg) { // [5]
System.out.println(msg);
}
}
public static void main(String[] args) {
Describe d = new Describe();
// 简单理解 映射,将describe.show() 映射给 Callable.call() 方法上
Callable c = d::show; // [6]
// 通过调用 call() 来调用 show(), java 将 call() 映射到show()
c.call("call()"); // [7]
// 静态方法引用
c = MethodReferences::hello; // [8]
c.call("Bob");
// 映射:将Description.help()方法映射到 Callable.call() 上
c = new Description("valuable")::help; // [9]
c.call("information");
c = Helper::assist; // [10]
c.call("Help!");
}
}
output:
call()
Hello, Bob
valuable information
Help!
未绑定的方法引用
未绑定的方法引用是指没有关联对象的普通(非静态)方法。
使用未绑定的引用时,我们必须先提供对象
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]
MakeString makeString = new MakeString() {
@Override
public String make() {
return "hah";
}
};
System.out.println(makeString.make());
// TransformX sp1 = X::f; 和下面表达的意思是一样的
TransformX sp = new TransformX() {
@Override
public String transform(X x) {
return x.f();
}
};
X x = new X();
System.out.println(sp.transform(x)); // [2]
System.out.println(x.f()); // Same effect
}
}
/* Output:
X::f()
X::f()
*/
- [1] 我们尝试把 X 的 f() 方法引用赋值给 MakeString。
- 虽然 make() 和 f() 具有相同的签名,但是编译不通过,会报无效引用(invalid method reference)错误。
- 这是因为实际上还有一个隐藏的参数:
this
。你不能在没有 X 对象的前提下调用 f() 。因此 X::f 表示未绑定的方法引用,因为它尚未绑定到对象。 - 要解决这个问题,我们需要一个 X 对象,索引我们接口中需要一个额外的参数,如上例的 TransformX
- 如果将 X :: f 赋值给 TransformX,在 Java 中是允许的。
- 使用未绑定的引用时,函数式方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配:原因是:需要一个对象来调用方法
未绑定方法与多参数的结合使用
class This {
void two(int i, double d) {
}
void three(int i, double d, String s) {
}
void four(int i, double d, String s, char c) {
}
}
interface TwoArgs {
void call2(This athis, int i, double d);
}
interface ThreeArgs {
void call3(This athis, int i, double d, String s);
}
interface FourArgs {
void call4(This athis, int i, double d, String s, char c);
}
public class MultiUnbound {
public static void main(String[] args) {
TwoArgs twoargs = This::two;
ThreeArgs threeargs = This::three;
FourArgs fourargs = This::four;
This athis = new This();
twoargs.call2(athis, 11, 3.14);
threeargs.call3(athis, 11, 3.14, "Three");
fourargs.call4(athis, 11, 3.14, "Four", 'Z');
}
}
构造函数引用
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;
}
}
interface MakeNoArgs {
Dog make();
}
interface Make1Arg {
Dog make(String nm);
}
interface Make2Args {
Dog make(String nm, int age);
}
public class CtorReference {
public static void main(String[] args) {
MakeNoArgs makeNoArgs = Dog::new;
Make1Arg make1Arg = Dog::new;
Make2Args make2Args = Dog::new;
Dog noArgs = makeNoArgs.make();
Dog args1 = make1Arg.make("ha");
Dog args2= make2Args.make("11",2);
}
}
函数式接口
-
@FunctionalInterface 函数式接口:限制接口中只能存在一个抽象方法
-
java 8黑魔法:自动适配函数式接口(适配你的值到目标接口),编译器会在后台把方法引用或lambda表达式包装进实现目标接口的类中
@FunctionalInterface
interface Functional {
String goodbye(String arg);
}
interface FunctionalNoAnn {
String goodbye(String arg);
}
// @FunctionalInterface 作用:接口中如果有多个抽象方法则会产生编译期错误
/*
@FunctionalInterface
interface NotFunctional {
String goodbye(String arg);
String hello(String arg);
}
Produces error message:
NotFunctional is not a functional interface
multiple non-overriding abstract methods
found in interface NotFunctional
*/
public class FunctionalAnnotation {
public String goodbye(String arg) {
return "Goodbye, " + arg;
}
public static void main(String[] args) {
FunctionalAnnotation fa = new FunctionalAnnotation();
// TODO: 2021/8/30 java 8黑魔法:自动适配函数式接口(适配你的值到目标接口),编译器会在后台把方法引用或lambda表达式包装进实现目标接口的类中
Functional f = fa::goodbye;
FunctionalNoAnn fna = fa::goodbye;
// Functional fac = fa; // Incompatible
Functional fl = a -> "Goodbye, " + a;
FunctionalNoAnn fnal = a -> "Goodbye, " + a;
}
}
函数式接口命名准则
-
如果只处理对象而非基本类型,名称则为 Function,Consumer,Predicate 等。参数类型通过泛型添加
-
如果接收的参数是基本类型,则由名称的第一部分表示,如 LongConsumer,DoubleFunction,IntPredicate 等,但返回基本类型的 Supplier 接口例外。
-
如果返回值为基本类型,则用 To 表示,如 ToLongFunction 和 IntToLongFunction。
-
如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 UnaryOperator,两个参数使用 BinaryOperator。
-
如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。
-
如果接收的两个参数类型不同,则名称中有一个 Bi。
特征 | 函数式方法名 | 示例 |
---|---|---|
无参数;无返回值 | 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<T,U> |
2 参数 Consumer;1引用;1 基本类型 | Obj 类型 Consumer accept() | ObjIntConsumer ObjLongConsumer ObjDoubleConsumer |
1 参数;返回类型不同 | Function apply() To 类型和 类型 To 类型<applyAs 类型 () | Function<T,R> 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<T,U> IntPredicate LongPredicate DoublePredicate |
参数基本类型;返回基本类型 | 类型 To 类型 Function applyAs 类型 () | 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 |
使用函数式接口时,名称无关紧要,只要参数类型和返回类型相同即可,Java会将你的方法映射到接口方法。
等同 final 效果
// 如果局部变量的初始值永远不会改变,那么它实际上就是 final 的。
class Closure2 {
// TODO: 2021/8/30 x等同 final 效果
IntSupplier makeFun(int x) {
int i = 0;
return () -> x + i;
}
}
class Closure3 {
IntSupplier makeFun(int x) {
int i = 0;
// TODO: 2021/8/30 被 Lambda 表达式引用的局部变量必须是 final 或者是等同 final 效果的
//return () -> x++ + i++; error
return () -> x;
}
}
class Closure4 {
IntSupplier makeFun(final int x) {
final int i = 0;
return () -> x + i;
}
}
函数组合
组合方法 | 支持接口 |
---|---|
andThen(argument) 根据参数执行原始操作 | Function BiFunction Consumer BiConsumer IntConsumer LongConsumer DoubleConsumer UnaryOperator IntUnaryOperator LongUnaryOperator DoubleUnaryOperator BinaryOperator |
compose(argument) 根据参数执行原始操作 | Function UnaryOperator IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
and(argument) 短路逻辑与原始谓词和参数谓词 | Predicate BiPredicate IntPredicate LongPredicate DoublePredicate |
or(argument) 短路逻辑或原始谓词和参数谓词 | Predicate BiPredicate IntPredicate LongPredicate DoublePredicate |
negate() 该谓词的逻辑否谓词 | Predicate BiPredicate IntPredicate LongPredicate DoublePredicate |
public class FunctionComposition {
// TODO: 2021/8/30 函数组合
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);
public static void main(String[] args) {
System.out.println(
f4.apply("GO AFTER ALL AMBULANCES"));
}
}
/* Output:
AFTER ALL AMBULANCES
_fter _ll _mbul_nces
*/
public class PredicateComposition {
// TODO: 2021/8/30 短路 与 或 非
static Predicate<String>
p1 = s -> s.contains("bar"),
p2 = s -> s.length() < 5,
p3 = s -> s.contains("foo"),
p4 = p1.negate().and(p2).or(p3);
public static void main(String[] args) {
Stream.of("bar", "foobar", "foobaz", "fongopuckey")
.filter(p4)
.forEach(System.out::println);
}
}