Skip to content

函数式编程

Lambda表达式

  • 简化函数式接口的使用

    // 定义一个函数式接口
    @FunctionalInterface
    interface MyFunction {
        int apply(int a, int b);
    }
    
    public class LambdaExample {
        public static void main(String [] args) {
            // 使用 Lambda 表达式实现函数式接口
            MyFunction add = (a, b) -> a + b;
            MyFunction subtract = (a, b) -> a - b;
    
            // 调用函数对象
            int result1 = add.apply(5, 3);
            int result2 = subtract.apply(5, 3);
    
            System.out.println("Addition result: " + result1);
            System.out.println("Subtraction result: " + result2);
        }
    }
    

  • ()->{}

  • 只有一个参数时可以省略(),没有参数时不可以省略
  • 只有一行代码时可以省略{}以及return

  • 递归函数fact = n->n==0?1:n*fact.call(n-1)

  • 对于lambda变量使用call进行调用

方法引用

  • 类名或对象名+::+方法名
  • 自然,非静态方法使用对象名,静态方法使用类名

    interface Callable {                        // [1]
      void call(String s);
    }
    
    class Describe {
      void show(String msg) {                   // [2]
        System.out.println(msg);
      }
    }
    
    public class MethodReferences {
      static void hello(String name) {          // [3]
        System.out.println("Hello, " + name);
      }
      static class Description {
        String about;
        Description(String desc) { about = desc; }
        void help(String msg) {                 // [4]
          System.out.println(about + " " + msg);
        }
      }
      static class Helper {
        static void assist(String msg) {        // [5]
          System.out.println(msg);
        }
      }
      public static void main(String [] args) {
        Describe d = new Describe();
        Callable c = d:: show;                   // [6]
        c.call("call()");                       // [7]
    
        c = MethodReferences:: hello;            // [8]
        c.call("Bob");
    
        c = new Description("valuable"):: help;  // [9]
        c.call("information");
    
        c = Helper:: assist;                     // [10]
        c.call("Help!");
      }
    }
    

  • 可以使用具有和接口相同签名(参数类型和返回值) 的方法引用来实现接口。

  • Thead对象创建要求传入实现Runnable接口的对象

      new Thread(Go:: go).start();
    

  • 未绑定的方法引用

  • 对于非静态方法不能直接通过类访问,因为这缺少 this 参数(要先创建对象,然后通过对象访问)
    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()); // Same effect
      }
    }
    
  • 如果接口参数多一个对象类型,就会自动创建对象!

  • 构造器方法引用

  • 使用参数匹配的接口捕获不同的构造方法
    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) {
          //名字都是 new
        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);
      }
    }
    

函数式接口

  • lambda表达式是函数式接口的实例,是与其关联的目标类型(即函数式接口描述lambda的类型,而lambda是特定类型函数式接口的一个具体实例)
  • 每个 Lambda 表达式都能隐式地赋值给函数式接口
  Runnable r = () -> System.out.println("hello world");
  • 函数式接口是只包含一个抽象方法声明的接口
  • 可以使用@FunctionalInterface(可选的,会检查是否满足只有一个抽象方法的条件)

  • 内置函数接口类型(java.util.function)

  • image-20231112235132480

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

  • 使用apply、get、compare等方法调用(见图片)
  • 方法引用也可以使用函数是接口

高阶函数

  • 函数作为参数或返回值的函数
  • 需要配合函数式接口使用
  • 把函数作为返回值
    //可以对内置的函数式接口进行重命名(通过继承)
    interface
    FuncSS extends Function <String, String> {}
    public class ProduceFunction {
      static FuncSS produce() {
        return s -> s.toLowerCase();//函数作为返回值
      }
      public static void main(String [] args) {
        FuncSS f = produce();
        System.out.println(f.apply("YELLING"));
      }
    }
    

闭包

  • 如果一个lambda使用了函数作用域之外的变量,解决这个问题就是支持闭包
  • 如果一个 lambda 表达式中包含了局部变量,这个变量必须为 final(至少不会被修改(即实际上的 final 变量))
  • 也可以将局部变量赋值给final变量再交给lambda表达式处理
  • 引用类型的是可以修改指向的对象的(设计final原理),但是包装类型不可以
  • 将内部类作为返回值时也需要注意类似的问题

杂项

  • 函数组合
  • 将多个函数结合使用
        return in.andThen(o -> {System.out.println(o);return o;});
    
  • f.andThen()在f之后调用
  • .compose()在f之前调用
  • 会得到一个新函数
  • image-20231113004543975

    //函数组合
    f4 = fa.compose(f2).andThen(f3);
    //逻辑
    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);
      }
    }
    //不包含并且 或者...
    

  • 柯里化和部分求值

  • 将一个接受多个参数的函数转变为一系列只接受一个参数的函数
  • 每接收一个新参数就得到一个新函数,直至得到结果
    import java.util.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 ");
           sum.apply("Hup ").apply("Ho ").apply("Hup");
          System.out.println(ho.apply("Hup"));
       }
    }