java函数式编程


简述

  函数本质上来说就是面向过程的程序设计的基本单元,Java不支持单独定义函数,但可以把静态方法视为独立的函数,把实例方法视为自带this参数的函数。函数式编程最早是数学家阿隆佐·邱奇研究的一套函数变换逻辑,又称Lambda Calculus(λ-Calculus),所以也经常把函数式编程称为Lambda计算。Java平台从Java 8开始,支持函数式编程和Lambda表达式。

Lambda表达式

  在Java程序中,我们经常遇到一大堆单方法接口,即一个接口只定义了一个方法,例如Comparator、Runnable

  以Comparator为例,我们想要调用Arrays.sort()时,可以传入一个Comparator实例,以匿名类方式编写如下:

String[] array = ...
Arrays.sort(array, new Comparator() {
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

  但从Java 8开始,我们可以用Lambda表达式替换单方法接口

String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
Arrays.sort(array, (s1, s2) -> {
    return s1.compareTo(s2);
});

  因为是单方法接口,所以方法的参数形式已经确定,返回值类型也已确定,所以一些不必要的东西如类的定义、方法的定义可以不写

@FunctionalInterface注解

  我们把只定义了单方法的接口称之为FunctionalInterface,用注解@FunctionalInterface标记

  这个注解的作用就是用来标识这个接口只有单方法,以及让编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错

方法引用

  除了Lambda表达式,我们还可以直接传入方法引用

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, Main::cmp);
        System.out.println(String.join(", ", array));
    }

    static int cmp(String s1, String s2) {
        return s1.compareTo(s2);
    }
}

  上述代码在Arrays.sort()中直接传入了静态方法cmp的引用,用Main::cmp表示

  因为Comparator接口定义的方法是int compare(String, String),和静态方法int cmp(String, String)相比,除了方法名外,方法参数一致,返回类型相同,因此,我们说两者的方法签名一致,可以直接把方法名作为Lambda表达式传入

  方法引用的例子:

lambda 等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack(); Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s) System.out::println

  可以总结为四种:

  • 静态方法:类名::方法名
  • 实例方法:类名::方法名
  • 现有对象的实例方法:对象名::方法名
  • 构造函数:类名::new

Function包

  java.util.function包里定义了JDK1.8有关的函数式编程接口,常用的有:

nametypedescription
Consumer Consumer< T > 接收T对象,不返回值
Predicate Predicate< T > 接收T对象并返回boolean
Function Function< T, R > 接收T对象,返回R对象
Supplier Supplier< T > 提供T对象(例如工厂),不接收值
UnaryOperator UnaryOperator 接收T对象,返回T对象
BinaryOperator BinaryOperator 接收两个T对象,返回T对象

Function接口

  apply方法:传入一个类型值(T)返回一个类型值(R)

R apply(T t);

  本质上其实就是实现了T -> R的Lambda表达式,这里会输出hello world123

Function function = new Function() {
    @Override
    public String apply(String s) {
        return s + "123";
    }
};
String str = function.apply("hello world");

  用Lambda表达式能简化Function接口的实现

Function function = x -> x + "123";
String str = function.apply("hello world");

  Function接口同样也能用于方法参数

public class Main {

    public static String validateValue(String name,Function function){
        return function.apply(name);
    }
    
    public static void main(String[] args) {
        String name="";
        System.out.println(validateValue(name,inputStr -> inputStr.isEmpty()?"不能为空":inputStr));
    }
    
}

  现在name会作为validateValue方法的参数和apply方法的参数,inputStr -> inputStr.isEmpty()?"不能为空":inputStr) 是apply方法的实现

Consumer接口

  accept方法:传入一个类型值,没有返回值

void accept(T t);

  本质上就是让t参数干点什么,没有返回值

String name="hello world";
Consumer consumer=new Consumer() {
    @Override
    public void accept(Object o) {
        System.out.println(o+"123");
    }
};
consumer.accept(name);

  同样也可以用Lambda表达式简化

Predicate接口

  Predicate一般用于做判断,返回boolean类型

boolean test(T t);

  例子:

String name="aaa";
Predicate predicate=new Predicate() {
    @Override
    public boolean test(Object o) {
        return o.equals("bbb");
    }
};
System.out.println(predicate.test(name));