跳转至

函数式编程

函数式编程

  • 函数式编程的意义:我们不用从头开始编写大量代码,而是从易于理解、充分测试及可靠的现有小块开始,最后将他们组合在一起创建新的代码

    OO(面向对象)是抽象数据,FP(函数式编程)是抽象行为

  • 函数式编程中额外的约束:
    • 不可变对象:所有数据必须不可变(设置一次,永不改变)
      • 将值传递给函数,该函数然后生成新值,但从不修改自身外部的任何东西(包括其参数或其函数范围之外的元素)
    • 无副作用:强制如此操作之后,所找到的任何错误都不是所谓副作用引起的

      这种范式解决了并发编程中最基本和最棘手的问题:可变共享状态的问题。 可变共享问题:代码的不同部分(在不同的处理器上运行)可以尝试同时修改同一块内存 在函数式编程的约束下,函数永远不会修改现有的值,指挥生成新的值,则不会对内存产生争用。 因此,经常提出纯函数是语言作为并行编程的一个解决方案

如果我们希望方法在调用时行为不同应该怎么做? 将代码传递给方法,我们就可以控制它的行为

[[Lambda表达式]]

  • Lambda表达式是使用最小可能语法编写的函数定义:
    • Lambda表达式产生函数,而不是类
    • Lambda语法尽可能少
      • 目的是为了使Lambda一遇编写和使用

        可以通过Lambda表达式在创建接口对应的对象时,实现接口中的方法

  • Lambda表达式的基本语法:
    • 参数
      • 当只有一个参数,可以不需要括号\
      • 为了保持一致性,正常情况使用括号包裹参数
      • 如果没有参数,则必须使用空括号()表示空参数
      • 多个参数必须使用括号
    • ->
    • 方法体
      • 对于单行的Lambda表达式方法体,禁止使用return语句
      • 如果要在Lambda表达式中写多行代码,则必须放在花括号中,此时可以使用return语句

递归

  • 递归函数是自我调用的函数
  • 当编写递归的Lambda表达式时,需要注意,递归方法必须是实例变量或静态变量,苟泽会出现编译错误
    package functional;  
    
    interface IntCall{  
        int call(int arg);  
    }  
    
    public class RecursiveFactorial {  
        static IntCall 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 */
    
package functional;  

public class RecursiceFibonacci {  
    IntCall fib;  //实例变量
    RecursiceFibonacci(){  
        fib = n -> n == 0 ? 0 :  
                   n == 1 ? 1:  
                   fib.call(n - 1) + fib.call(n - 2);  
    }  

    public static void main(String[] args) {  
        RecursiceFibonacci rf = new RecursiceFibonacci();  
        for (int i = 0; i < 10; i++) {  
            System.out.println(rf.fib.call(i));  
        }  
    }  
}  
/**  
 * 0 
 * 1 
 * 1
 * 2
 * 3
 * 5 
 * 8 
 * 13
 * 21
 * 34
 * */

方法引用

  • 方法引用组成:类名或对象名::方法名

Runnable接口

  • 它符合单方法接口格式,因此我们可以使用Lambda表达式和方法引用作为Runnable
    • 方法run()不带参数,也没有返回值
      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("Annoymous");  
                  }  
              }).start();  
      
              new Thread(  
                      ()-> System.out.println("labmda")  
              ).start();  
      
              new Thread(Go::go).start();  
          }  
      }  
      /**  
       * Annoymous
       * labmda 
       * Go::go
       */
      

      Thread对象将Runnable作为其构造函数参数,并具有会调用run()的方法 注意:只有匿名内部类才需要具有名为run()的方法

未绑定的方法引用

  • 未绑定的方法引用是指没有关联对象的普通(非静态)方法
  • 使用未绑定的引用时,我们必须先提供对象
    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) {  
    //        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());  //相同效果  
        }  
    }  
    /**  
     * X::f() 
     * X::f() 
     */
    
  • [1]中,报错的原因是:我们在没有X对象的前提下调用f()
    • 要解决这个问题,我们必须要有一个X对象,如TransformX.transform(X x)

构造函数的引用

  • 可以捕获构造函数的引用,然后通过引用调用该构造函数:类名::new

[[Lambda表达式#函数式接口|函数式接口]]

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());  
    }  
}

闭包

  • 闭包:用于生成函数

    考虑一个更复杂的Lambda,它使用函数作用域之外的变量。返回该函数会发生什么>也就是说,当你调用函数时,它对那些外部变量引用了什么?如果语言不能自动解决,那问题将变的非常棘手。能够解决这个问题的语言被称为支持闭包,或者叫做在词法上限定范围。

    package functional;  
    
    import java.util.function.IntSupplier;  
    class Closure1 {  
    
        int i;  
        IntSupplier makeFun(int x){  
            return () -> x + i++;  
        }  
    }  
    public class SharedStorage {  
        public static void main(String[] args) {  
            Closure1 c1 = new Closure1();  
            IntSupplier i1 = c1.makeFun(0);  
            IntSupplier i2 = c1.makeFun(0);  
            IntSupplier i3 = c1.makeFun(0);  
            System.out.println(i1.getAsInt());  
            System.out.println(i2.getAsInt());  
            System.out.println(i3.getAsInt());  
        }  
    }
    /**
    * 0
    * 1
    * 2
    */
    
    可以看到,这些函数都共享了i的存储空间
    class Closure5{  
    //无法编译通过
        IntSupplier makeFun(int x){  
            int i;  
            return () -> i + x
            ;  
        }  
    }
    
    被lambda表达式引用的局部变量必须是final或是等同final效果的
    public Closure6{
        IntSupplier makeFun(int x){
            int i = 0;
            x++;
            i++;
            final int iFinal = i;
            final int xFinal = x;
            return () -> xFinal + iFinal;
        }
    }
    
    等同final效果:虽然没有明确声明变量是final的,但是因变量值没被改变过而是基友了final同等的效果。如果局部变量的初始值永远不会改变,那么它实际上就是final 在lambda表达式中声明的变量,都是final,因此不能在lambda表达式中修改局部变量的值