美文网首页程序员
说说 Java 中的函数式编程

说说 Java 中的函数式编程

作者: deniro | 来源:发表于2020-07-05 09:30 被阅读0次

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

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

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

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

    1 Lambda 表达式

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

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

    找不到 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 表达式,最后调用接口所定义的方法:

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


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

    1.2 Lambda 表达式的基本语法

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

    2 Java 8 方法引用

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

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

    3 函数式接口

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

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

    3.1 标准函数式接口

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

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

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

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

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

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

    最后还有一个 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 表达式赋值给函数式接口:

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

    3.3 函数组合( FunctionComposition )

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

    3.4 自定义函数式接口

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

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

    相关文章

      网友评论

        本文标题:说说 Java 中的函数式编程

        本文链接:https://www.haomeiwen.com/subject/cgglqktx.html