欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

说说 Java 中的函数式编程

程序员文章站 2022-07-14 20:31:49
...

Java8 提供了以下两种方式,来支持函数式编程。

  1. Lambda 表达式
  2. 方法引用 (MethodReferences)

Bruce Eckel 举了一个策略模式的示例,来比较传统写法与函数式编程写法之间的区别。

说说 Java 中的函数式编程

说说 Java 中的函数式编程

首先定义一个策略接口,然后我们就可以实现不同的策略,以供其它业务类使用。在此可以看到,传统的匿名内部类代码量最多, lambda 表达式代码居中,而方法引用最精简。

1 Lambda 表达式

Lambda 表达式( lambda expression )是一个匿名函数, Lambda 表达式基于数学中的 λ 演算得名,直接对应于其中的 lambda 抽象( lambda abstraction),是一个匿名函数,即没有函数名的函数。

λ 演算, λ (Lambda(大写 Λ ,小写 λ )读音: ['læ;mdə]) 演算是一套用于研究函数定义 、 函数应用和递归的形式系统。它由 Alonzo Church 和 Stephen Cole Kleene 在 20 世纪三十年代引入, Church 运用 lambda 演算在 1936 年给出判定性问题的一个否定的答案。

说说 Java 中的函数式编程

找不到 Alonzo Church 和 Stephen Cole Kleene 的照片,这张大家看看就好 O(∩_∩)O

1.1 各种 Lambda 表达式

首先先定义好结果接口:

interface Description {
  String brief();
}

interface Body {
  String detailed(String head);
}

interface Multi {
  String twoArg(String head, Double d);
}

然后定义 Lambda 表达式,最后调用接口所定义的方法:

说说 Java 中的函数式编程

示例演示了各种情况,比如带括号 、 空括号 、 带多个参数 、 方法体多行并且写在括号中等等。除了最后一行,其它的 Lambda 表达式方法体都是单行的,单行表达式的结果会自动成为 Lambda 表达式的返回值。如果在 Lambda 表达式中需要多行,那么必须将这些行放在花括号中。在这种情况下,需要使用 return。


可以看到 Lambda 表达式最大的特点就是简洁、易读。

1.2 Lambda 表达式的基本语法

  1. 参数。
  2. 接着 ->,可视为“产出”。
  3. -> 之后的内容都是方法体。
  • 当只用一个参数,可以不需要括号 ()。 这是特例。
  • 正常情况括号 () 包裹参数。 为了保持一致性,也可以使用括号 () 包裹单个参数,虽使用然这种情况并不常见。
  • 如果没有参数,则必须使用括号 () 表示空参数列表。
  • 对于多个参数,将参数列表放在括号 () 中。
  1. Lambda 表达式方法体都是单行的,单行表达式的结果会自动成为 Lambda 表达式的返回值。
  2. 如果在 Lambda 表达式中需要多行,那么必须将这些行放在花括号中。 在这种情况下,需要使用 return。

2 Java 8 方法引用

Java 8 方法引用语法是:类名或对象名,后面跟 ::,然后跟方法名称。

说说 Java 中的函数式编程

说说 Java 中的函数式编程

这里首先定义了一个 Callable 接口,内含一个带 String 入参的 call() 方法;接着定义了一个类,内含一个带 String 入参的 show() 方法;最后定义了了一个类,内含一个带 String 入参的静态 hello() 方法;在使用方法引用时, Java 会认为这三个方法的签名相同(也可以说是与接口方法同名的方法引用),所以都可以使用方法引用语法,赋给 Callable 对象。当调用c.call() 方法时,Java 会根据实际的实例对象,调用实际的方法。比如 c.call(“Bob”) 方法,实际调用的是 MethodReferences 的 hello() 方法。

3 函数式接口

方法引用和 Lambda 表达式必须被赋值,赋值的对象类型会告诉编译器,编译器会保障类型正确。

说说 Java 中的函数式编程

x 和 y 可以是任何支持 + 运算符连接的数据类型,可以是两个不同的数值类型或者是1个 String 加任意一种可自动转换为 String 的数据类型(这包括了大多数类型)。但是,当 Lambda 表达式被赋值时,编译器必须确定 x 和 y 的确切类型以生成正确的代码。

3.1 标准函数式接口

Java8 引入了 java.util.function 包。它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型。每个接口只包含一个抽象方法,称为函数式方法。

说说 Java 中的函数式编程

之前所描述的6个基本的接口,每一个都有3个变种,分别用于操作原生类型 int , long 和 double。

说说 Java 中的函数式编程

其名字衍生自这些基本的接口,只不过每一个都在前面加上了一个原生类型。

Function 接口还定义了 9 个变种,用在结果类型为原生类型的场景。如果源与结果类型都是原生类型,那就在 Function 前加上 SrcToResult。 如果源是对象引用,结果是原生类型,那就在 Function 前加上 ToResult。

说说 Java 中的函数式编程

Function 接口还定义了两参数版本,它们以 Bi 作为接口名前缀:

说说 Java 中的函数式编程

如果一个 Consumer 函数接口有两个参数,一个是对象引用,另一个是原生类型。那么会以 ObjXXXConsumer 作为函数接口名称。其中的 XXX 表示原生类型。

说说 Java 中的函数式编程

最后还有一个 Boolean Supplier 接口,它是 Supplier 的变种,返回 boolean 值。这是在所有标准的函数式接口名当中,唯一一个显式使用 boolean 类型的;我们也可以通过 Predicate 及其4个变种来得到 boolean 返回值。


Java8 总共定义了 43 个标准的函数式接口。确实有些多,不过并不太可怕,大多数标准的函数式接口存在的唯一目的在于为原生类型提供支持。

3.2 赋值

首先自定义类:

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

然后通过 Lambda 表达式赋值给函数式接口:

说说 Java 中的函数式编程

最后调用 Function 接口中不同类型的 apply() 方法,就可以调用与其关联的 Lambda 表达式:

说说 Java 中的函数式编程

3.3 函数组合( FunctionComposition )

函数组合指的是把多个函数组合起来,生成新函数。2个是执行先后顺序组合,另外3个是逻辑组合。

说说 Java 中的函数式编程

说说 Java 中的函数式编程

3.4 自定义函数式接口

如果标准函数式接口没有我们所需要的接口怎么办?没关系,JDK8 提供了可自定义接口的方式。比如,我们可以这样定义三个入参的函数接口。示例演示了采用方法引用与 lambdas 方式。

说说 Java 中的函数式编程

关键点是使用 @FunctionalInterface 注解来自定义函数式接口。

相关标签: Java