Java函数式编程

前言

现在的 Java 项目大量用到了函数式编程,为了巩固相关知识,写一篇文章记录一下学习心得。
Java 并不是函数式语言,但是从 Java 8 开始的 Lambda 表达式和方法引用允许我们用 Java 语言来进行函数式编程。

通常,传递给方法的数据不同,结果不同,如果我们希望方法在调用的时候行为不同,只需要将代码传递给方法,也就是把方法传递给方法,就可以控制方法的行为,这就是函数式编程其中一个意义所在,下面来看一下 Java 是如何支持函数式编程的。

Lambda 表达式

Lambda 表达式是使用最小可能语法来编写函数,看下面例子:


interface Service {
    String print(String name);
}

class Basketball implements Service {
    @Override
    public String print(String name) {
        return name + "在打篮球";
    }
}

class People {
    Service service;
    String name;
    People(String name) {
        this.name = name;
    }
    void setService(Service service) {
        this.service = service;
    }
    void process() {
        System.out.println(service.print(name));
    }

}

public class Test {
    public static void main(String[] args) {
        People people = new People("小明");
        Service service = new Service() {
            @Override
            public String print(String name) {
                return name + "在打篮球";
            }
        }; // 1
        people.setService(service);
        people.process();

        service = new Basketball(); // 2
        people.setService(service);
        people.process();

        service = (name -> name + "在打篮球"); // 3
        people.setService(service);
        people.process();
        
        service = (a -> a + "在踢足球"); // 4
        people.setService(service);
        people.process();
    }
}

复制代码

输出结果:

小明在打篮球
小明在打篮球
小明在打篮球
小明在踢足球
复制代码

上面的代码用三种不同的方式实现了同一个功能,目的是为了让 People 类型的对象调用自身的 process() 方法来打印输出相关信息。

  1. 创建一个匿名内部类,存在冗余代码。
  2. 常规做法,麻烦。
  3. Lambda 表达式,用 -> 来分割参数和函数体,箭头左边是参数,箭头右边是函数体,当函数体只有一行时,表达式的返回值就是函数的返回值,这实现了与匿名内部类、定义类相同的效果,但代码少很多。
  4. 这里可以看到 -> 左边的参数可以任意命名,只要 Lambda 函数体和返回值与 Service 接口中的方法体一一对应即可,原本小明的行为是打篮球,现在我把踢足球的方法传给了他,他就在踢足球了,这里我们传递的是方法,也就是把方法当作一个对象来进行传递,妙啊!

当然 Lambda 表达式还有其他语法变体:


interface Description {
  String brief();
}

interface Body {
  String detailed(String head);
}

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

public class LambdaExpressions {

  static Body bod = h -> h + " No Parens!"; // 1

  static Body bod2 = (h) -> h + " More details"; // 2

  static Description desc = () -> "Short info"; // 3

  static Multi mult = (h, n) -> h + n; // 4

  static Description moreLines = () -> { // 5
    System.out.println("moreLines()");
    return "from moreLines()";
  };

  public static void main(String[] args) {
    System.out.println(bod.detailed("Oh!"));
    System.out.println(bod2.detailed("Hi!"));
    System.out.println(desc.brief());
    System.out.println(mult.twoArg("Pi! ", 3.14159));
    System.out.println(moreLines.brief());
  }
}

复制代码

输出结果:

Oh! No Parens!
Hi! More details
Short info
Pi! 3.14159
moreLines()
from moreLines()

复制代码
  1. 当只用一个参数,可以不需要括号 () 然而,这是一个特例。
  2. 正常情况使用括号 () 包裹参数。 为了保持一致性,也可以使用括号 () 包裹单个参数,虽然这种情况并不常见。
  3. 如果没有参数,则必须使用括号 () 表示空参数列表。
  4. 对于多个参数,将参数列表放在括号 () 中。到目前为止,所有 Lambda 表达式方法体都是单行。 该表达式的结果自动成为 Lambda 表达式的返回值,在此处使用 return 关键字是非法的。 这是 Lambda 表达式缩写用于描述功能的语法的另一种方式。
  5. 如果在 Lambda 表达式中确实需要多行,则必须将这些行放在花括号中。 在这种情况下,就需要使用 return

方法引用

方法引用语法:类名或对象名::方法名称。

interface Service { // 1
    String print(String name);
}

class Swim { 
    public String swim(String name) { // 2
        return name + "在游泳";
    }
}

class Jijian {
    public static String jijian(String name) { // 3
        return name + "在击剑";
    }
}

public class Test {
    public static void main(String[] args) {
        Swim swim = new Swim();
        Service service = swim::swim; // 4
        System.out.println(service.print("小明"));

        service = Jijian::jijian; // 5
        System.out.println(service.print("小明"));
    }
}
复制代码

输出结果:

小明在游泳
小明在击剑
复制代码
  1. 从单一接口方法开始(接口中只能有一个抽象方法)。
  2. swim() 的签名(参数类型和返回类型)符合 Serviceprint() 签名。
  3. jijian() 也符合。
  4. Swim 对象的方法引用赋值给 Service ,它没有 swim() 方法,而是 print() 方法,但是 Java 接受这样看似奇怪的赋值,因为方法引用符合函数的签名。
  5. 静态方法直接用类名来引用即可。

总结:OO(object oriented,面向对象) 是抽象数据,FP(functional programming,函数式编程) 是抽象行为。

Java 函数式编程远不止这些,本文参考了《On Java 8》,是《Java编程思想》的作者写的,详情可参考下方链接:
github.com/kyiree/Ling…