概述

JDK 提供了大量常用的函数式接口以丰富 Lambda 的典型使用场景.它们主要在java.util.function包中被提供. 下面是最简单的几个接口及使用示例.

Supplier 接口

java.util.function.Supplier<T>接口仅包含一个无参的方法: T get(). 用来获取一个泛型参数指定类型的对象数据. 由于这是一个函数式接口, 这也就意味着对应的 Lambda 表达式需要 “对外提供” 一个符合泛型类型的对象数据.

import java.util.function.Supplier;

public class Test {
    private static String getString(Supplier<String> function) {
        return function.get();
    }

    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        System.out.println(getString(() -> msgA + msgB));
    }
}
练习: 求数组元素最大值

题目

使用 Supplier 接口作为方法参数类型, 通过 Lambda 表达式求出 int 数组中的最大值. 提示: 接口泛型请使用java.lang.Integer类.

解答

import java.util.function.Supplier;

public class Test {
    // 定义一个方法, 方法的参数传递Supplier, 泛型使用Integer
    private static int getMax(Supplier<Integer> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        int[] array = {2, 3, 4, 52, 333, 23};

        // 调用getMax方法, 参数传递Lambda
        int maxNum = getMax(() -> {
            // 计算数组的最大值
            int max = array[0];
            for (int i = 1; i < array.length; i++) {
                if (array[i] > max) {
                    max = array[i];
                }
            }
            return max;
        });
        System.out.println(maxNum);
    }
}

输出结果:
333
Consumer 接口

java.util.function.Consumer<T>接口则正好与 Supplier 接口相反, 它不是生产一个数据, 而是消费一个数据, 其数据类型由泛型决定.

抽象方法: accept

Consumer 接口中包含抽象方法```void accept(T t), 意为消费一个指定泛型的数据. 基本使用如下:

import java.util.function.Consumer;

public class Test {
    private static void consumeString(Consumer<String> consumer){
        consumer.accept("Hello");
    }

    public static void main(String[] args) {
        consumeString(s -> System.out.println(s));
    }
}

输出结果:
Hello

默认方法: andThen

如果一个方法的参数和返回值全都是 Consumer 类型, 那么就是可以实现效果: 消费数据的时候, 首先做一个操作, 实现组合. 而这个方法就是 Consumer 接口中的 default 方法 andThen. JDK 源码如下:

default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) ‐> { accept(t); after.accept(t); };
}

注: java.util.Objects的 requireNonNull 静态方法将会在参数为 null 时主动抛出NullPointerException异常. 这省去了重复编写 if 语句和抛出空指针异常的麻烦.

想要实现组合, 需要两个或者多个 Lambda 表达式即可, 而 andThen 的语义正是 “一步接一步” 操作. 例如两个步骤组合的情况:

import java.util.function.Consumer;

public class Test104 {
    private static void consumeString(Consumer<String> one, Consumer<String> two) {
        one.andThen(two).accept("Hello");
    }

    public static void main(String[] args) {
        consumeString(
                s -> System.out.println(s.toUpperCase()),
                s -> System.out.println(s.toLowerCase())
        );
    }
}

输出结果:
HELLO
hello

运行结果将会首先打印完全大写的 HELLO, 然后打印完全小写的 hello. 当然, 通过链式写法可以实现更多步骤的组合.

练习: 格式化打印信息

题目

下面的字符串数组当中存有多条信息, 请按照格式 “姓名: xx. 性别: xx.” 的格式将信息打印出来. 要求将打印姓名的动作作为第一个 Consumer 接口的 Lambda 实例, 将打印性别的动作作为第二个 Consumer 接口的 Lambda 实例, 将两个 Consumer 接口按照顺序 “拼接” 到一起.

public static void main(String[] args) {
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
    }

解答

import java.util.function.Consumer;

public class Test105 {
    private static void consumeString(Consumer<String> c1, Consumer<String> c2, String[] array) {
        for (String temp : array) {
            c1.andThen(c2).accept(temp);
        }
    }

    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
        consumeString(
                s -> System.out.println("姓名: " + s.split(",")[0]),
                s -> System.out.println("性别: " + s.split(",")[1]),
                array
        );
    }
}

输出结果:
姓名: 迪丽热巴
性别: 女
姓名: 古力娜扎
性别: 女
姓名: 马尔扎哈
性别: 男
Predicate 接口

有时候我们需要对某种类型的数据进行判断, 从而得到一个 boolean 值结果. 这时可以使用java.util.function.Predicate<T>接口.

抽象方法: test

Predicate 接口中包含一个抽象方法: boolean test(T t). 用于条件判断的场景, 代码如下:

import java.util.function.Predicate;

public class Test {
    private static void method(Predicate<String> predicate) {
        boolean veryLong = predicate.test("HelloWorld");
        System.out.println("字符串很长吗: " + veryLong);
    }

    public static void main(String[] args) {
        method(s -> s.length() > 5);
    }
}

输出结果:
字符串很长吗: true

条件判断的标准是传入的 Lambda 表达式逻辑, 只要字符串长度大于 5 则认为很长.

默认方法: and

既然是条件判断, 就会存在与, 或, 非三种常见的逻辑关系. 其中将两个 Predicate 条件使用 “与” 逻辑连接起来实现 “并且” 的效果时, 可以使用 default 方法 and. 其 JDK 源码为:

default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) ‐> test(t) && other.test(t);
}

如果要判断一个字符串既要包含大写 “H”, 又要包含大写 “W”, 那么:

import java.util.function.Predicate;

public class Test107 {
    private static void method(Predicate<String> predicate) {
        boolean result = predicate.test("HelloWorld");
        System.out.println("字符串符合要求吗: " + result);
    }

    public static void main(String[] args) {
        method(s -> s.contains("H") && s.contains("W"));
    }
}

或者:

import java.util.function.Predicate;

public class Test107 {
    private static void method(Predicate<String> p1, Predicate<String> p2) {
        boolean result = p1.and(p2).test("HelloWorld");
        System.out.println("字符串符合要求吗: " + result);
    }

    public static void main(String[] args) {
        method(s -> s.contains("H"), s -> s.contains("W"));
    }
}

默认方法: or

与 and 的 “与” 类似, 默认方法 or 实现逻辑关系中的 “或”. JDK 源码为:

default Predicate<T> or(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) ‐> test(t) || other.test(t);
}

如果希望实现逻辑 “字符串包含大写 H 或包含大写 W”, 那么代码只需要将 “and” 修改为 “or” 名称即可. 其他都不变:

import java.util.function.Predicate;

public class Test {
    private static void method(Predicate<String> p1, Predicate<String> p2) {
        boolean result = p1.or(p2).test("HelloWorld");
        System.out.println("字符串符合要求吗: " + result);
    }

    public static void main(String[] args) {
        method(s -> s.contains("H"), s -> s.contains("W"));
    }
}

默认方法: negate

“与”, “或” 我们已经了解, 剩下的 “fei” (取反) 也不会太难. 默认方法 negate 的 JDK 源码为:

default Predicate<T> negate() {
   return (t) ‐> !test(t);
}

从现实中很容易看出, 它是执行了 test 方法之后, 对结果 boolean 进行 “!” 取反而已. 一定要在 test 方法调用之前调用 negate 方法, 正如 and 和 or 方法一样:

import java.util.function.Predicate;

public class Test {
    private static void method(Predicate<String> predicate) {
        boolean veryLong = predicate.negate().test("HelloWorld");
        System.out.println("字符串很长吗: " + veryLong);
    }

    public static void main(String[] args) {
        method(s -> s.length() > 5);
    }
}

输出结果:
字符串很长吗: false
练习: 集合信息筛选

题目

数组当中有多条 “姓名 + 性别” 的信息如下, 请通过 Predict 接口的拼装将符合要求的字符串筛选到集合 ArrayList 中, 需要同时满足两个条件:

  1. 必须为女士
  2. 姓名为 4 个字
public class Test {
    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
    }
}

解答

import java.util.ArrayList;
import java.util.function.Predicate;

public class Test {
    private static ArrayList<String> filter(String[] array, Predicate<String> p1, Predicate<String> p2) {
        ArrayList<String> arrayList = new ArrayList<>();
        for (String temp : array) {
            if (p1.and(p2).test(temp)) {
                arrayList.add(temp);
            }
        }
        return arrayList;
    }

    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
        ArrayList<String> filtered_array = new ArrayList();
        filtered_array = filter(
                array,
                s -> s.split(",")[0].length() == 4,
                s -> s.split(",")[1].equals("女")
        );
        System.out.println("过滤后的数组: " + filtered_array.toString());
    }
}

输出结果:
过滤后的数组: [迪丽热巴,女, 古力娜扎,女]
Function 接口

java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据, 前者称为前置条件, 后者称为后置条件.

抽象方法: apply

Function 接口中最主要的抽象方法为: R apply(T t), 根据类型 T 的参数获取类型 R 的结果.

使用的常见例如: 将 String 类型转换为 Integer 类型.

import java.util.function.Function;

public class Test {
    private static void method(Function<String, Integer> function) {
        int num = function.apply("10");
        System.out.println(num + 10);
    }

    public static void main(String[] args) {
        method(s -> Integer.parseInt(s));
    }
}

输出结果:
20

默认方法: andThen

Function 接口中有一个默认的 andThen 方法, 用来进行组合操作. JDK 源代码如下:

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) ‐> after.apply(apply(t));
}

该方法同样用于 “先做师门, 再做什么” 的场景, 和 Consumer 中的 andThen 差不多:

import java.util.function.Function;

public class Test {
    private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
        int num = one.andThen(two).apply("10");
        System.out.println(num + 10);
    }

    public static void main(String[] args) {
        method(s -> Integer.parseInt(s) + 10, i -> i *= 10);
    }
}

输出结果:
210

第一个操作是字符串解析成为 int 数字, 第二个操作是乘以 10. 两个操作通过 andThen 按照前后顺序组合到了一起.

注: Function 的前置条件泛型和后置条件泛型可以相同.

练习: 自定义函数模型拼接

题目

请使用 Function 进行函数模型的拼接, 按照顺序需要执行的多个函数操作为:

String str = "赵丽颖,20:;
  1. 将字符串截取数字年龄部分, 得到字符串
  2. 将上一步的字符串转换称为 int 类型的数字
  3. 将上一步的 int 数字累加 100, 得到结果 int 数字

解答

import java.util.function.Function;

public class Test {
    private static int getAgeNum(
            String str,
            Function<String, String> one,
            Function<String, Integer> two,
            Function<Integer, Integer> three
    ) {
        return one.andThen(two).andThen(three).apply(str);
    }

    public static void main(String[] args) {
        String str = "赵丽颖,20";
        int age = getAgeNum(
                str,
                s -> s.split(",")[1],
                s -> Integer.parseInt(s),
                i -> i += 100
        );
        System.out.println("获得的年龄: " + age);
    }
}

输出结果:
获得的年龄: 120