Java 函数式编程知识整理

87 阅读3分钟

函数式接口

函数式接口只有一个抽象方法的接口。如果定义函数式接口只需在接口上方加入@FunctionalInterface注解。函数式接口中可以有类方法默认方法等,但只能有一个抽象方法

@FunctionalInterface
interface Foo {
  void foo();
  
  static int sum(int a, int b) {
    // 可以有类方法
    return a + b;
  }
  
  default void bar() {
    // 可以有默认方法
    System.out.println("bar");
  }
}

Lambda 表达式出现之前,通常使用匿名内部类来创建实例:

Foo f = new Foo() {
  @Override
  public void foo() {
    System.out.println("匿名内部类测试...");
  }
};
f.foo();    // 匿名内部类测试...

System.out.println(Foo.sum(1, 2)); // 3
f.bar();    // bar

Lambda 表达式

由以上代码可见,使用匿名内部类时会有大量多余的代码(如new Foo()、重写唯一的抽象方法)。Java 8 引入了 Lambda 表达式,用于简化代码。

Lambda 表达式本质上就是函数式接口的匿名内部类。

以上代码使用 Lambda 表达式则为:

Foo f = () -> {
  System.out.println("匿名内部类测试...");
};

如果该抽象方法有参数,参数不可省略

例如:

@FunctionalInterface
interface Foo {
  void foo(String name);
}
public class LambdaTest {
  public static void main(String[] args) {
    Foo f = (String name) -> {   // 参数类型可以省略
      System.out.println("欢迎回来," + name);
      System.out.println("——来自Lambda表达式");
    };
    f.foo("巴掌");
    // 欢迎回来,巴掌
    // ——来自Lambda表达式
  }
}

Lambda 表达式简化

Lambda 表达式可以有如下简化:

  • 形参列表中参数类型可以省略;
  • 如果形参列表只有一个参数,那么圆括号可以省略;
  • 如果方法体只有一行代码,花括号可以省略;
  • 如果方法体只有一行代码,且为return语句,return可以省略。

例如:

@FunctionalInterface
interface Foo {
  int foo(int a);
}
public class LambdaTest {
  public static void main(String[] args) {
    Foo f = a -> a * a;
    System.out.println(f.foo(5));   // 25
  }
}

方法引用和构造器引用 (重点)

当方法体只有一条代码时才可以使用方法引用本质是进一步省略形参列表,形参列表顺序与传入参数的顺序完全相同。

引用类方法

引用类方法转化前的格式为(参数列表) -> 类.类方法(参数列表),转化后为类::类方法

@FunctionalInterface
interface Foo {
  double min(double i1, double i2);
}

public class Test {
  public static void main(String[] args) {
//  Foo f = (i1, i2) -> Math.min(i1, i2);
    Foo f = Math::min;
    System.out.println(f.min(5.0, 2.0));  // 2.0
  }
}

引用实例方法

引用实例方法转化前的格式为(参数1, 其他参数) -> 参数1.实例方法(其他参数),转化后为类::实例方法

@FunctionalInterface
interface Foo {
  String sub(String s, int start, int end);
}

public class Test {
  public static void main(String[] args) {
//  Foo f = (s, start, end) -> s.substring(start, end);
    Foo f = String::substring;
    System.out.println(f.sub("Java, yes!", 1, 4));  // ava
  }
}

引用特定对象的实例方法

引用特定对象的实例方法转化前的格式为(参数) -> 某对象.实例方法(参数),转化后为某对象::实例方法

@FunctionalInterface
interface Foo {
  String sub(int start, int end);
}

public class Test {
  public static void main(String[] args) {
//  Foo f = (start, end) -> "Java, yes!".substring(start, end);
    Foo f = "Java, yes!"::substring;
    System.out.println(f.sub(1, 4));  // ava
  }
}

构造器引用

构造器引用转化前的格式为(参数) -> 类.构造器(参数),转化后为类::new

@FunctionalInterface
interface Foo {
  StringBuilder foo(String s);
}

public class Test {
  public static void main(String[] args) {
//  Foo f = s -> new StringBuilder(s);
    Foo f = StringBuilder::new;
    System.out.println(f.foo("Welcome to Java World!"));
  }
}