函数式编程
13.3方法引用
13.3.2未绑定的方法引用
未绑定的方法引用是指没有关联对象的普通(非静态)方法。使用未绑定的引用时,必须先提供对象:
public class UnboundMethodReference {
public static void main(String[] args) {
// 即使make和f具有相同的签名,编译也会报错。这是因为实际上还有另一个隐藏的参数:this。
// 不能在没有X对象的前提下调用f。因此,X::f表示未绑定的方法引用,因为它尚未绑定到对象
// MakeString ms = X::f;
// 使用未绑定的引用时,函数式方法的签名不再与方法引用的签名完全匹配。
// 原因是:需要一个对象来调用方法
TransformX sp = X::f;
X x = new X();
System.out.println(sp.transform(x));
System.out.println(x.f());
}
}
class X {
String f() {
return "X::f()";
}
}
interface MakeString {
String make();
}
interface TransformX {
String transform(X x);
}
如果方法有更多个参数,就以第一个参数接受
this
的模式来处理:
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 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);
}
13.4函数式接口
基本命名准则:
- 如果只处理对象而非基本类型,名称则为
Function
、Consumer
、Predicate
等,参数类型通过泛型添加。- 如果接收的参数是基本类型,则由名称的第一部分表示,如
LongConsumer
、DoubleFunction
、IntPredicate
等,但返回基本类型的Supplier
接口例外。- 如果返回值为基本类型,则用
To
表示,如ToLongFunction<T>
和IntToLongFunction
。- 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用
UnaryOperator
,两个参数使用BinaryOperator
。- 如果接收两个参数且返回值为布尔值,则名称中有一个
Bi
。
13.5高阶函数
高阶函数只是一个消费或产生函数的函数。
public class ProduceFunction {
// 使用lambda表达式,可以轻松地在方法中创建和返回一个函数
static FuncSS produce() {
return s -> s.toLowerCase();
}
public static void main(String[] args) {
FuncSS f = produce();
System.out.println(f.apply("YELLING"));
}
}
// 使用继承,可以轻松地为专用接口创建别名
interface FuncSS extends Function<String, String> {}
13.6闭包
public class Closure1 {
int i;
// 调用makeFun之前需要先创建对象,当对同一个对象多次调用makeFun,
// 最终会得到多个函数,它们共享i的存储空间
IntSupplier makeFun(int x) {
return () -> x + i++;
}
}
public class SharedStorage {
public static void main(String[] args) {
Closure1 c1 = new Closure1();
IntSupplier f1 = c1.makeFun(0);
IntSupplier f2 = c1.makeFun(0);
IntSupplier f3 = c1.makeFun(0);
System.out.println(f1.getAsInt());
System.out.println(f2.getAsInt());
System.out.println(f3.getAsInt());
}
}
被lambda表达式引用的局部变量必须是
final
或者是等同final
效果的,即虽然没有明确地声明变量是final
的,但是因变量值没被改变过而实际有了final
同等的效果,如果局部变量的初始值永远不会改变,那么它实际上就是final
的。
如果函数式方法中使用的外部局部变量是引用,而不是基本类型的话:
public class Closure7 {
IntSupplier makeFun(int x) {
Integer i = 0;
// 编译器识别到变量i的值被更改过
i = i + 1;
return () -> x + i;
}
}
public class Closure8 {
// 每次调用makeFun时,其实都会创建并返回一个全新而非共享的ArrayList,
// 也就是说,每个闭包都有自己独立的ArrayList,它们之间互不干扰
Supplier<List<Integer>> makeFun() {
final List<Integer> ai = new ArrayList<>();
// 改变List的内容却没产生编译时错误
ai.add(1);
return () -> ai;
}
public static void main(String[] args) {
Closure8 c7 = new Closure8();
List<Integer> l1 = c7.makeFun().get();
List<Integer> l2 = c7.makeFun().get();
l1.add(42);
l2.add(96);
System.out.println(l1);
System.out.println(l2);
}
}
13.8柯里化和部分求值
柯里化意为:将一个多参数的函数,转换为一系列单参数函数。
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;
System.out.println(uncurried("Hi", "Ho"));
Function<String, String> hi = sum.apply("Hi ");
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
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
处理基本类型和装箱时,需要使用适当的函数式接口:
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