Java---函数式编程

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 接口是函数式接口,只有一个抽象方法。同时 Callablecall() 方法签名(返回类型与参数类型)与 Describe类的 show 方法签名相同,所以可以将Describeshow方法引用赋值给 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; 该语句试图把 Xf()方法引用赋值给 MakeString。虽然 make() 与 f() 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。
这是因为忽略了一个隐藏的参数 thisXf() 是一个普通方法,需要有 X对象的才能调用 f(), 此时 X::f()是一个未绑定的方法引用。需要额外添加一个 X 对象的参数才能成功赋值引用。如TransformXtransform方法。

构造函数引用

语法: 类名::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包中函数接口基本命名准则:

  • 如果只处理对象而非基本类型,名称则为 FunctionConsumerPredicate 等。参数类型通过泛型添加。

  • 如果接收的参数是基本类型,则由名称的第一部分表示,如 LongConsumerDoubleFunctionIntPredicate 等,但基本 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之前。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容