on java 8 第十三章 函数式编程
函数式编程的中心思想:是把函数作为参数传递给另一个函数,或把函数作为一个返回值。
只是为了使程序适合有限的内存,程序员通过修改内存中的代码来节省代码空间,以便在程序执行时执行不同的操作。这种技术被称为自修改代码 (self-modifying code)
OO(object oriented,面向对象)是抽象数据,
FP(functional programming,函数式编程)是抽象行为
纯粹的函数式语言在安全性方面更进一步。它强加了额外的约束,即所有数据必须是不可变的:设置一次,永不改变。
“不可变对象和无副作用”范式解决了并发编程中最基本和最棘手的问题之一
因此,经常提出纯函数式语言作为并行编程的解决方案(还有其他可行的解决方案)。
1 新旧对比
package functional; interface Strategy { String approach(String msg); } class Soft implements Strategy { @Override public String approach(String msg) { return msg.toLowerCase() + "?"; } } class Unrealted { static String twice(String msg) { return msg + " " + msg; } } public class Strategize { Strategy strategy; String msg; Strategize(String msg){ strategy = new Soft(); // [1]在 Strategize 中,Soft 作为默认策略,在构造函数中赋值。 this.msg = msg; } void communicate(){ System.out.println(strategy.approach(msg)); } void changeStategy(Strategy strategy){ this.strategy = strategy; } public static void main(String[] args) { Strategy[] strategies = { new Strategy() {// [2]匿名内部类 @Override public String approach(String msg) { return msg.toUpperCase() + "!"; } }, msg -> msg.substring(0,5),// [3]Java 8 的 Lambda 表达式 // 由箭头 -> 分隔开参数和函数体,箭头左边是参数,箭头右侧是从 Lambda 返回的表达式,即函数体。这实现了与定义类、匿名内部类相同的效果,但代码少得多。 Unrealted::twice// [4]Java 8 的方法引用,由 :: 区分。在 :: 的左边是类或对象的名称,在 :: 的右边是方法的名称,但没有参数列表。 }; Strategize s = new Strategize("Hello there"); s.communicate(); for (Strategy newStrategy : strategies){//遍历数组中的所有 Strategy s.changeStategy(newStrategy);// [5]将每个 Strategy 放入 变量 s 中 s.communicate();// [6]产生不同的行为,具体取决于此刻正在使用的策略代码对象.我们传递的是行为,而非仅数据。 } } } /* hello there? HELLO THERE! Hello Hello there Hello there */
2 Lambda表达式
package functional; 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!"; //当只用一个参数,可以不需要括号 ()。 然而,这是一个特例。 static Body bod2 = (h) -> h + " More details"; //正常情况使用括号 () 包裹参数。 为了保持一致性,也可以使用括号 () 包裹单个参数,虽然这种情况并不常见。 static Description desc = () -> "Short info"; //如果没有参数,则必须使用括号 () 表示空参数列表。 static Multi mult = (h, n) -> h + n; // 对于多个参数,将参数列表放在括号 () 中。 //到目前为止,所有 Lambda 表达式方法体都是单行。 该表达式的结果自动成为 Lambda 表达式的返回值,在此处使用 return 关键字是非法的。 这是 Lambda 表达式缩写用于描述功能的语法的另一种方式。 static Description moreLines = () -> { //如果在 Lambda 表达式中确实需要多行,则必须将这些行放在花括号中。 在这种情况下,就需要使用 return。 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()); } } /* Oh No Parens! Hi More details Short info pi 3.14159 moreLines() from moreLines() */
2.1 递归
递归方法必须是实例变量或静态变量,否则会出现编译时错误。
package functional; interface IntCall{ int call(int arg); }
阶乘函数
package functional; public class RecursiveFactorial { static IntCall fact; //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 3628800 */
package functional; public class RecursiveFibonacci { IntCall fib; //实例变量 RecursiveFibonacci() { fib = n -> n == 0 ? 0 : n == 1 ? 1 : fib.call(n - 1) + fib.call(n - 2); } int fibonacci(int n) { return fib.call(n); } public static void main(String[] args) { RecursiveFibonacci rf = new RecursiveFibonacci(); for (int i = 0; i <= 10; i++) System.out.println(rf.fibonacci(i)); } } /* 0 1 1 2 3 5 8 13 21 34 55 */
3 方法引用
Java 8 方法引用没有历史包袱。方法引用组成:类名或对象名,后面跟 :: ,然后跟方法名称。
package functional; interface Callable { // [1] void call(String s); } class Describe { void show(String msg) {// [2] show() 的签名(参数类型和返回类型)符合 Callable 的 call() 的签名。 System.out.println(msg); } } public class MethodReferences { static void hello(String name) {// [3] hello() 也符合 call() 的签名。 System.out.println("Hello," + name); } static class Description { String about; Description(String desc) { about = desc; } void help(String msg) { // [4] //help() 也符合,它是静态内部类中的非静态方法。 System.out.println(about + " " + msg); } } static class Helper { static void assist(String msg) {// [5] //assist() 是静态内部类中的静态方法。 System.out.println(msg); } } public static void main(String[] args) { Describe d = new Describe(); Callable c = d::show; // [6] 我们将 Describe 对象的方法引用赋值给 Callable ,它没有 show() 方法,而是 call() 方法。 但是,Java 似乎接受用这个看似奇怪的赋值,因为方法引用符合 Callable 的 call() 方法的签名。 c.call("call()"); // [7] 可以通过调用 call() 来调用 show(),因为 Java 将 call() 映射到 show()。 c = MethodReferences::hello; // [8] 静态方法引用 c.call("Bob"); c = new Description("valueable")::help; // [9] 这是 [6] 的另一个版本:对已实例化对象的方法的引用,有时称为绑定方法引用。 c.call("information"); c = Helper::assist; // [10] 获取静态内部类的方法引用的操作与 [8] 中外部类方式一样 c.call("Help!"); } } /* call() Hello,Bob valueable information Help! */
3.1 Runnable接口
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("Anonymous"); } }).start(); new Thread(() -> System.out.println("lambda")).start();//Lambda 表达式 new Thread(Go::go).start();//方法引用 } } /* Anonymous lambda Go::go() */
3.2 未绑定的方法引用
未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用之前,我们必须先提供对象
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) { // 即使 make() 与 f() 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 // 这是因为实际上还有另一个隐藏的参数:我们的老朋友 this。 你不能在没有 X 对象的前提下调用 f()。 // 因此,X :: f 表示未绑定的方法引用,因为它尚未“绑定”到对象 // MakeString ms = X::f; // [1] TransformX sp = X::f; X x =new X(); System.out.println(sp.transform(x));// [2] [2] 的结果有点像脑筋急转弯。 我接受未绑定的引用并对其调用 transform(),将其传递给 X,并以某种方式导致对 x.f() 的调用。 Java 知道它必须采用第一个参数,这实际上就是 this,并在其上调用方法。 System.out.println(x.f());// 同等效果 } } /* X::f() X::f() */
未绑定的方法与多参数的结合运用
package functional; 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'); } }
3.2 构造函数引用
package functional; 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 mna = Dog::new;// [1] Make1Arg m1a = Dog::new;// [2] Make2Args m2a = Dog::new;// [3] Dog dn = mna.make(); Dog d1 = m1a.make("Comet"); Dog d2 = m2a.make("Ralph",4); } } //我们如何对 [1],[2] 和 [3] 中的每一个使用 Dog :: new。 这 3 个构造函数只有一个相同名称::: new,但在每种情况下都赋值给不同的接口。编译器可以检测并知道从哪个构造函数引用。 // //编译器能识别并调用你的构造函数( 在本例中为 make())。
4 函数式接口
每个接口只包含一个抽象方法,称为函数式方法。
package functional; @FunctionalInterface interface Functional{ String goodbye(String arg); } interface FunctionalNoAnn{ String goodbye(String arg); } /* @FunctionalInterface interface NotFunctional{ String goodbye(String arg); String hello(String arg); } 产生错误信息: 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(); // Java 8 在这里添加了一点小魔法: // 如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配), // Java 会适配你的赋值到目标接口。 // 编译器会自动包装方法引用或 Lambda 表达式到实现目标接口的类的实例中。 Functional f = fa::goodbye; FunctionalNoAnn fna = fa::goodbye; // Functional fac = fa;// Incompatible Functional f1 = a -> "Goodbye, " + a; FunctionalNoAnn fnal = a -> "Goodbye, " + a; } }
基本命名准则:
如果只处理对象而非基本类型,名称则为 Function,Consumer,Predicate 等。参数类型通过泛型添加。
如果接收的参数是基本类型,则由名称的第一部分表示,如 LongConsumer,DoubleFunction,IntPredicate 等,但基本 Supplier 类型例外。
如果返回值为基本类型,则用 To 表示,如 ToLongFunction 和 IntToLongFunction。
如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 UnaryOperator,两个参数使用 BinaryOperator。
如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。
如果接收的两个参数类型不同,则名称中有一个 Bi。
下面枚举了基于 Lambda 表达式的所有不同 Function 变体的示例
package functional; 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); } }
方法引用
package functional; import java.util.function.BiConsumer; class In1{} class In2{} public class MethodConversion { static void accept(In1 i1,In2 i2){ // 只要参数类型、返回类型与 BiConsumer 的 accept() 相同即可。 System.out.println("accept()"); } static void someOtherName(In1 i1,In2 i2){ //只要参数类型、返回类型与 BiConsumer 的 accept() 相同即可。 System.out.println("someOtherName()"); } public static void main(String[] args) { BiConsumer<In1,In2> bic; bic = MethodConversion::accept; bic.accept(new In1(),new In2()); bic = MethodConversion::someOtherName; bic.accept(new In1(),new In2()); } } /* accept() someOtherName() */
基于类的函数式,应用于方法引用
创建最简单的函数式签名
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()); } }
4.1 多参数函数式接口
package functional; @FunctionalInterface public interface TriFunction<T,U,V,R> { R apply(T t, U u, V v); }
验证 测试了方法引用和 Lambda 表达式
package functional; 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; } }
4.2 缺少基本类型的函数
package functional; import java.util.function.BiConsumer; public class BitConsumerPermutations { static BiConsumer<Integer,Double> bicid =(i,d) -> System.out.printf("%d, %f%n",i,d); // %n 跨平台 static BiConsumer<Double,Integer> bicdi = (d,i) -> System.out.printf("%d, %f%n",i,d); static BiConsumer<Integer,Long> bicil = (i, l) -> System.out.printf("%d, %d%n",i,l); public static void main(String[] args) { bicid.accept(47,11.34); bicdi.accept(22.45,92); bicil.accept(1,11L); } } /* 47, 11.340000 92, 22.450000 1, 11 */
package functional; import java.util.function.Function; import java.util.function.IntToDoubleFunction; public class FunctionWithWrapped { public static void main(String[] args) { // Function<Integer,Double> fid = i -> i; // Integer cannot be converted to Double Function<Integer,Double> fid = i -> (double)i; IntToDoubleFunction fid2 = i -> i; } }
5 高阶函数
高阶函数(Higher-order Function)只是一个消费或产生函数的函数。
产生函数 package functional; import java.util.function.Function; interface FuncSS extends Function<String,String> {} // [1] 继承 public class ProduceFunction { static FuncSS produce(){ return s -> s.toLowerCase(); // [2] lambda表达式 } public static void main(String[] args) { FuncSS f = produce(); System.out.println(f.apply("YELLING")); } }
消费函数
package functional; import java.util.function.Function; class One{} class Two{} public class ConsumeFunction { static Two consume(Function<One,Two> onetwo){ return onetwo.apply(new One()); } public static void main(String[] args) { Two two = consume(one -> new Two()); } }
package functional; import java.util.function.Function; class I { public I(){ System.out.println("Create I"); } @Override public String toString(){ return "I"; } } class O{ public O(){ System.out.println("Create O"); } @Override public String toString(){ return "O"; } } public class TransformFunction { static Function<I,O> transform(Function<I,O> in){ System.out.println("2"); return in.andThen(o -> { System.out.println(o); return o; }); } public static void main(String[] args) { System.out.println("1"); Function<I,O> f2 = transform(i -> { System.out.println("lalla"); System.out.println(i); return new O(); }); System.out.println("3"); O o = f2.apply(new I()); } } /* 1 2 3 Create I lalla I Create O */
6 闭包
6.1 作为闭包的内部类
使用匿名内部类重写之前的例子
package functional; import java.util.function.IntSupplier; public class AnonymousClosure { IntSupplier makeFun(int x){ int i = 0; // 同样规则的应用: // i++; // 非等同 final 效果 // x++; // 同上 return new IntSupplier() { @Override public int getAsInt() { return x + i ; } }; } }
7 函数组合
package functional; import java.util.function.Function; 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); // 创建的新函数 f4。它调用 apply() 的方式与常规几乎无异. // 当 f1 获得字符串时,它已经被f2 剥离了前三个字符。这是因为 compose(f2) 表示 f2 的调用发生在 f1 之前。 public static void main(String[] args) { System.out.println(f4.apply("GO AFTER ALL AMBULANCES")); } } /* AFTER ALL AMBULANCES _fter _ll _mbul_nces */
package functional; import java.util.function.Predicate; import java.util.stream.Stream; public class PredicateComposition { 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); } } /* foobar foobaz */
8 柯里化和部分求值
柯里化(Currying)名称来自于其发明者之一 Haskell Curry
将一个多参数的函数,转换为一系列单参数函数。
package functional; import java.util.function.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")); } } /*HiHo HiHo Hup Ho Hup Hey */
通过添加级别来柯里化一个三参数函数
package functional; import java.util.function.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")); } } /* 对于每个级别的箭头级联(Arrow-cascading),你在类型声明中包裹了另一个 Function。 Hi Ho Hup */
处理基本类型和装箱时,请使用适当的 Function 接口
package functional; import java.util.function.IntFunction; import java.util.function.IntUnaryOperator; 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 纯函数式编程
要确保一切是 final 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,我们无法通过编译器查错。