Java8 一个重要的特性是通过 Lambda 表达式和方法引用 (Method References) 支持了函数式编程。使得代码更加简洁。
函数式编程的意义:通过合并现有代码来生成新功能而不是从头开始编写所有内容。
OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。
Lambda 表达式
Lambda 一个重要的作用是:简化了匿名内部类的写法,在 Java8 之前,如果要编写一个匿名内部类,例如创建一个线程
new Thread(new Runnable(){
public void run(){
System.out.println("Create Thread");
}}).start();
而在 Java8 使用 Lambda 可以更加简洁的写法
new Thread( () -> System.out.println("Create Thread") ).start()
从上面的例子可以看出,Lambda 表达式就相当于一段没有函数名的匿名函数。
Lambda 语法
Lambda 表达式是使用最小可能语法编写的函数定义:
(参数) -> {方法体}
- 参数的类型不需要显示声明,编辑器会自动识别:
(变量名)->{方法体}
- 参数只有一个的情况可以不带括号:
变量 -> {方法体}
- 参数多个或者零个的时候就需要带上括号:
(变量1,变量2)-> {方法体}
- 当方法体中只有一条语句的时候可以不用加大括号:
() -> System.out.println("")
如果表达式中需要有返回值,该表达式的结果自动成为 Lambda 表达式的返回值,在此处使用 return 关键字是非法的。 - 如果在 Lambda 表达式中确实需要多行,则必须将这些行放在花括号中。 在这种情况下,就需要使用 return。
方法引用
语法:类名或对象名::方法名
// 函数式接口,只有一个抽象类
interface Callable {
void call(String s);
}
class Describe {
void show(String msg) {
System.out.println(msg);
}
}
public class MethodReferences {
static void hello(String name) { // [3]
System.out.println("Hello, " + name);
}
public static void main(String[] args) {
Describe d = new Describe();
// 将 d 对象的 show 方法引用赋值给 c
Callable c = d::show;
c.call("call");
// 将 MethodReferences 类的 static 方法引用赋值给 c
c = MethodReferences::hello
}
}
在上面例子 Callable c = d::show;
语句中,d 对象将 show()
方法的引用赋值给 c,因为 Callable
接口是函数式接口,只有一个抽象方法。同时 Callable
的call()
方法签名(返回类型与参数类型)与 Describe
类的 show
方法签名相同,所以可以将Describe
的show
方法引用赋值给 Callable
。赋值后 call
就有了与show
相同的方法体。
未绑定的方法引用
未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用之前,我们必须先提供对象:
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()); // 同等效果
}
}
MakeString ms = X::f;
该语句试图把 X
的f()
方法引用赋值给 MakeString
。虽然 make() 与 f() 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。
这是因为忽略了一个隐藏的参数 this
, X
的 f()
是一个普通方法,需要有 X
对象的才能调用 f()
, 此时 X::f()
是一个未绑定的方法引用。需要额外添加一个 X
对象的参数才能成功赋值引用。如TransformX
的transform
方法。
构造函数引用
语法: 类名::new
有多个构造函数的情况下,编辑器会根据接口的方法签名(参数列表和返回值)自动匹配相应的构造函数引用
函数式接口
函数式接口的定义:接口中有且只有一个抽象方法。
接口满足定义默认就是函数式接口或者可以添加 @FunctionalInterface 解强制执行此“函数式方法”模式。
如 Runnable 接口就只包含 run 这个抽象方法。在创建线程使用 Thread 类时,使用 Lambda 表达式书写匿名函数,编辑器会自动匹配到 run 方法并进行类型推导,同时 Lambda 表达式中的参数个数需要同接口中抽象的方法的参数个数相等。
@FunctionalInterface
interface Functional {
String goodbye(String arg);
}
interface FunctionalNoAnn {
String goodbye(String arg);
}
java.util.function
包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。这主要是因为基本类型会产生一小部分接口。 如果你了解命名模式,顾名思义就能知道特定接口的作用。
以下是 java.util.function
包中函数接口基本命名准则:
如果只处理对象而非基本类型,名称则为
Function
,Consumer
,Predicate
等。参数类型通过泛型添加。如果接收的参数是基本类型,则由名称的第一部分表示,如
LongConsumer
,DoubleFunction
,IntPredicate
等,但基本Supplier
类型例外。如果返回值为基本类型,则用
To
表示,如ToLongFunction <T>
和IntToLongFunction
。如果返回值类型与参数类型一致,则是一个运算符:单个参数使用
UnaryOperator
,两个参数使用BinaryOperator
。如果接收两个参数且返回值为布尔值,则是一个谓词(
Predicate
)。如果接收的两个参数类型不同,则名称中有一个
Bi
。
可以使用这些接口来使用方法引用,可以减少自己创建函数式接口
高阶函数
高阶函数:一个消费或产生函数的函数。
// 使用继承,此处只是为了给专用接口 Function 创建别名 FuncSS
interface
FuncSS extends Function<String, String> {} // [1]
public class ProduceFunction {
// 产生一个函数
static FuncSS produce() {
return s -> s.toLowerCase(); // [2]
}
// 消费一个函数
static String consume(Function<String,String> fun) {
return fun.apply(new String());
}
public static void main(String[] args) {
FuncSS f = produce();
System.out.println(f.apply("YELLING"));
}
}
produce()
是高阶函数,使用了 Lambda 返回一个函数
闭包
闭包:能够使用函数作用域以外的变量
Lambda 引用的局部变量必须是 final 修饰的等同与 final 的效果的值。(初始值用永远不会改变)。而引用对象的字段时,则不必是 final 属性
public class Closure1 {
int i;
IntSupplier makeFun(int x) {
return () -> x + i++;
}
}
public class Closure2 {
IntSupplier makeFun(int x) {
int i = 0;
// x++ 和 i++ 都会报错:
return () -> x++ + i++;
}
}
函数组合
函数组合(Function Composition)意为“多个函数组合成新函数”。
同与数据之间进行的 “与或非”类似。不同之处在于函数组合操作的对象是函数。
- andThen(argument):函数调用之后调用
- compose(argument):函数调用之前调用
- and(argument) :短路逻辑与原始断言和参数断言
- or(argument) :短路逻辑或原始断言和参数断言
- negate() :该断言的逻辑否断言
public class FunctionComposition {
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"));
}
}
输出
AFTER ALL AMBULANCES
_fter _ll _mbul_nces
当 f1
获得字符串时,它已经被 f2
剥离了前三个字符。这是因为 compose(f2)
表示 f2
的调用发生在 f1
之前。