Java8函数式接口详解

目录

一、Supplier 接口

1、将 Supplier 直接使用

2、将 Supplier 使用生成对象

3、将 Supplier 做为方法的参数

二、Consumer 接口

1、直接使用 Consumer 对象

2、Consumer 做为参数

3、使用 Consumer 做为参数

三、Predicate 接口

1、test()方法演示

2、默认方法 and()

3、默认方法 or()

4、默认方法 negate()

5、静态方法 isEqual ()

6、Predicate 的应用示例

四、Function 接口

1、抽象方法:apply()

2、默认方法:andThen()

3、默认方法:compose()

五、BinaryOperator 接口

1、方法的演示:apply()

2、BinaryOperator 接口做为方法参数

3、静态方法的演示

六、UnaryOperator 接口

UnaryOperator 使用方法的参数

七、常用的函数式接口小结


Java8函数式接口

接口参数返回中文实例
Supplier NoneT提供者工厂方法创建对象
Consumer TNone消费者消费一个数据,无返回
Predicate Tboolean判断/谓词对指定条件进行测试
Function TR函数根据一个参数得到另一个参数值

一、Supplier 接口

Supplier 接口代表一个结果的提供者。

Supplier 接口是用来生成数据的,数据的类型通过泛型参数给定,使用 get()方法获得返回值。

接口的源码如下:

@FunctionalInterface
public interface Supplier<T> {
    /**
     * 得到一个结果,返回 T 对象
     * @return a result
     */
    T get();
}

Supplier 接口中的方法

T get() 获得指定类型的数据

1、将 Supplier 直接使用

案例需求:get()方法的基本使用,在 Supplier 接口中 get 方法返回一个字符串。 

案例步骤:

  1. 使用 Lambda 创建 Supplier 对象,泛型类型为 String,方法体返回一个字符串,return 可以省略。
  2. 调用 Supplier 的 get()方法得到字符串,并打印输出
public class LambdaTest {
    public static void main(String[] args) {
        //使用 Lambda 创建 Supplier 对象
        Supplier supplier = () -> "Hello Java!";
        //输出它的值
        System.out.println(supplier.get());
    }
}
// 输出
// Hello Java!

2、将 Supplier 使用生成对象

案例需求:

下面的例子演示了如何通过调用一个静态方法,生成一个员工对象返回。使用构造方法做为 Supplier 参数的引用。

案例步骤:

  1. 在主类内部创建一个私有的静态 Employee 对象,重写 toString()方法,返回一个字符串:"我是员工"。
  2. 在 main 函数中创建一个 Supplier 对象,泛型类型是 Employee。使用 Lambda 传入 Supplier 对象,方法体实例化员工对象,省略 return 方法。
  3. 使用 supplier 对象的 get()方法得到员工对象
  4. 打印输出员工对象

因为 Employee 对象是私有的,外部类无法直接实例化员工对象。

调用 Supplier 的 get()方法来生成员工对象,这样做的目的是可以控制员工对象的生成方式,类似于工厂模式。

public class LambdaTest {

    public static void main(String[] args) {
        //使用 Lambda 传入 Supplier 对象,将生成一个员工对象
        //此时仅仅是实例化了接口并未执行里面代码
        Supplier supplier = ()->new Employee();
        //输出员工对象
        System.out.println(supplier.get());
    }

    //员工类
    private static class Employee {//注意static
        @Override
        public String toString() {
            return "我是员工";
        }
    }
}
// 输出
// 我是员工

3、将 Supplier 做为方法的参数

需求说明:求数组中的最大值,使用 Supplier 接口作为方法参数类型,通过 Lambda 表达式求出 int 数组中的最大值。

需求分析:

  1. 定义整型数组 int[] arr = {12,68,10,2,99,313,46};
  2. 创建静态方法 getMax():返回 int 类型,将 Supplier 做为参数,泛型类型为 Integer,方法体调用 get()方法返回值。
  3. 在 main 函数中调用 getMax()方法,使用 Lambda 传入 Supplier 对象,并且实现查找最大值的功能。
  4. 在 Lambda 表达式相当于方法体:遍历每个元素,比较大小,找出最大值。
public class LambdaTest {

    public static void main(String[] args) {
        int[] arr = {12, 68, 10, 2, 99, 313, 46};
        // 调用 getMax 方法获得最大值,Lambda 相当于方法体
        int num = getMax(() -> {
            int max = arr[0];
            for (int i = 1; i < arr.length; i++) {
                if (max < arr[i]) {
                    max = arr[i];
                }
            }
            return max;
        });
        //输出最大值
        System.out.println("最大值是:" + num);
    }
    
    //使用 Supplier 做为参数
    public static int getMax(Supplier<Integer> supplier) {
        return supplier.get();
    }
}
// 输出
// 最大值是:313

二、Consumer 接口

Consumer 接口代表接受单一的输入变量而且没有返回值的一类操作。 ​ 它的作用和 Supplier 相反,是消费一个数据的,消费的数据类型需要通过泛型指定。

源代码如下:

@FunctionalInterface
public interface Consumer<T> {

    /**
     * 接受 t 对象,无返回值
     */
    void accept(T t);

    /**
     * 默认的组合方法,参数和返回值都是 Consumer 类型
     * 先调用自己的 accept()方法,再调用参数的 accept()方法
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Consumer 接口中的方法

void accept(T t) 接受对给定的参数进行操作。

Consumer 接口中的默认方法:

default Consumer<T>  andThen(Consumer<T>  after)

如果一个方法的参数和返回值全都是 Consumer<T>  类型,那么就可以实现效果:

消费一个数据的时候,首先做一个操作,然后再做另一个操作,两个操作依次执行,实现一种组合操作。而这个方法就是 Consumer 接口中的默认方法 andThen。

1、直接使用 Consumer 对象

实现步骤:

  1. 使用 Lambda 创建 Consumer 对象,直接打印传入的字符串数据。
  2. 调用 Consumer 的 accept()方法,在 accept()方法中传入一个字符串数据。
public class LambdaTest {

    public static void main(String[] args) {
        //创建 Consumer 对象,打印传入的变量 t
        Consumer consumer = t -> System.out.println(t);
        //调用 Consumer 中的方法
        consumer.accept("Hello Lambda");
    }
}
// 输出
// Hello Lambda

2、Consumer 做为参数

List 和 Set 集合中遍历的 forEach 方法它的参数就是 Consumer,请看下面的代码:

案例需求:

  1. 创建一个数组,使用 Arrays.asList("孙悟空", "猪八戒", "白骨精", "嫦娥") 转成 List 对象。
  2. 使用 forEach 方法打印每一个元素,forEach 中使用 Lamba 表达式输出传入的字符串
public class LambdaTest {
    public static void main(String[] args) {
        //将数组转成 List 对象
        List<String> names = Arrays.asList("孙悟空", "猪八戒", "白骨精", "嫦娥");
        //打印每一个字符串,forEach 的参数就是 Consumer
        names.forEach(t -> System.out.println(t));
    }
}
// 输出
//孙悟空
//猪八戒
//白骨精
//嫦娥

分析 forEach()方法的源代码

default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

这是定义在 java.lang.Iterable 接口中的默认方法,参数就是 Consumer 对象,方法体内对当前集合使用 for遍历,this 就是集合对象。每次对一个元素调用 accept()方法。而我们外部调用的代码中对 accept()方法进行了实现,输出了每个元素。

​public static  T requireNonNull(T obj) 静态方法,JDK7 中新增的方法,判断传入的对象是否为 NULL,如果是 NULL 则抛出异常,不为 NULL 则返回对象本身。常用于方法或构造方法中传入对象参数的校验。

default Consumer<T>  andThen(Consumer<? super T>  after) {
     //判断 after 是否为 null
     Objects.requireNonNull(after);
     //先调用自己的 accept()方法,再调用参数的 accept()方法
     return (T t) -> { accept(t); after.accept(t); };
}
​
要想实现组合,需要两个或多个 Lambda 表达式,而 andThen 的语义正是执行“一步接一步”操作。

案例需求:将字符串 Hello 首先打印大写的 HELLO,然后打印小写的 hello

实现步骤:

  1. 创建 Consumer 对象 c1,使用 Lambda 打印 s 对象的大写
  2. 创建 Consumer 对象 c2,使用 Lambda 打印 s 对象的小写
  3. c1 调用 andThen(c2)方法,再调用 accept("字符串"),完成依次的操作。
public class LambdaTest {
    public static void main(String[] args) {
        //打印大写
        Consumer<String> c1 = s -> System.out.println(s.toUpperCase());
        //打印小写
        Consumer<String> c2 = s-> System.out.println(s.toLowerCase());
        //调用方法
        c1.andThen(c2).accept("Hello Consumer");
    }
}
// 输出
// HELLO CONSUMER
// hello consumer

3、使用 Consumer 做为参数

需求说明:

格式化打印信息,下面的字符串数组当中存有多条信息,请按照格式“姓名:XX。性别:XX。”的格式将信息打印出来。要求将打印姓名的动作作为第一个 Consumer 接口的 Lambda 实例,将打印性别的动作作为第二个Consumer 接口的 Lambda 实例,将两个 Consumer 接口按照顺序“拼接”到一起。以下数组共 5 个元素,每个元素包含 2 项信息用逗号分隔。

String[] arr = { "张飞,男", "貂蝉,女", "曹操,男","孙尚香,女","小乔,女" };

实现步骤

  1. 创建静态方法 printInfo(),有 3 个参数,第 1 个是需要打印的字符串数组,第 2 个是 Consumer用于打印姓名 name,第 3 个是 Consumer用于打印性别 gender。
  2. 在 printInfo 方法中遍历数组中每个元素,再调用 name.andThen(gender).accept(单个元素)
  3. 每调用一次 andThen()方法,在下面输出一行横线
  4. 在 main 函数中创建上面要遍历的数组
  5. 调用 printInfo 方法,传入 3 个参数,第 1 个参数是数组,第 2 个参数使用 Lambda 打印姓名,参数 s 表示数组中的每个元素。第 3 个参数使用 Lambda 打印性别。
public class LambdaTest {

    public static void main(String[] args) {
        String[] arr = {"张飞,男", "貂蝉,女", "曹操,男","孙尚香,女"};
        //这里的 s 表示数组中的每个元素
        printInfo(arr,
                s ->System.out.println("姓名:" + s.split(",")[0]),
                s ->System.out.println("性别:" + s.split(",")[1]));
    }

    public static void printInfo(String[] arr, Consumer<String> name, Consumer<String> gender) {
        for (String s : arr) {
            name.andThen(gender).accept(s);
            System.out.println("------------------");
        }
    }
}

三、Predicate 接口

Predicate 中文意思为谓语,"我是一个程序员","是"或"不是"就是谓语。

它代表只有一个变量的函数,返回 boolean 类型。有时候我们需要进行某种判断,从而得到一个 boolean 值的结果。可以使用 java.util.function.Predicate接口。

@FunctionalInterface
public interface Predicate<T> {

    /**
     * 抽象方法,对 t 进行测试,返回 boolean 类型
     */
    boolean test(T t);

    /**
     * 组合方法,将当前的谓语与另一个谓语进行短路的与操作,返回一个谓语对象
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     * 对当前的谓语进行逻辑非操作,返回一个谓语对象
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     * 组合方法,将当前的谓语与另一个谓语进行短路的或操作,返回一个谓语对象
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     * 静态方法,判断 test(object)方法传入的对象是否与参数 targetRef 对象相等
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Predicate 接口中的方法

boolean test(T t) 对 t 进行指定条件的测试,返回 boolean 类型

1、test()方法演示

案例需求:判断 test("字符串")方法给定的参数长度是否大于 5

案例步骤:

  1. 创建一个 Predicate 谓语对象,使用 Lambda 实现 boolean test(T t)方法
  2. 方法体的参数是 s,返回字符串的长度大于 5,省略 return 关键字。
  3. 两次调用 test()方法看运行结果,第 1 次使用字符串 Hello,第 2 次使用字符串 Predicate
public class LambdaTest {

    public static void main(String[] args) {
        //创建一个 Predicate 谓语对象,boolean test(T t)方法接收字符串类型,返回 boolean 类型
        Predicate<String> predicate = s -> s.length() > 5;
        //两次调用 test 方法看运行结果
        System.out.println("Hello 的长度是否大于 5:" + predicate.test("Hello"));
        System.out.println("Predicate 的长度是否大于 5:" + predicate.test("Predicate"));
    }
}
// 执行结果
// Hello 的长度是否大于 5:false
// Predicate 的长度是否大于 5:true

2、默认方法 and()

既然是条件判断,就会存在与、或、非三种常见的逻辑关系。 ​ 其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用 default 方法 and。 ​ 这个默认方法接收一个 Predicate 参数,返回一个 Predicate参数。

其 JDK 源码为:

/**
     * 组合方法,将当前的谓语与另一个谓语进行短路的与操作,返回一个谓语对象
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

and 方法演示示例:

案例需求:判断一个字符串是否包含指定的字符串:既包含大写“H”,又要包含大写“W”

案例步骤:

  1. 创建 2 个需要判断的字符串:s1="Hello world"和 s2="Hello World"
  2. 使用 Lambda 表达式,创建两个 Predicate 对象
  3. 判断字符串 s 是否包含 H
  4. 判断字符串 s 是否包含 W
  5. 调用 and 方法和 test 方法,分别输出 s1 和 s2 的结果
public class LambdaTest {

    public static void main(String[] args) {
        //创建 2 个需要判断的字符串
        String s1 = "Hello world";
        String s2 = "Hello World";
        // 使用 Lambda 表达式,创建两个 Predicate 对象
        //判断 s 是否包含 H
        Predicate<String> p1 = s -> s.contains("H");
        //判断 s 是否包含 W
        Predicate<String> p2 = s -> s.contains("W");
        //调用 and 方法
        System.out.println(s1 + "是否包含 H 和 W:" + p1.and(p2).test(s1));
        System.out.println(s2 + "是否包含 H 和 W:" + p1.and(p2).test(s2));
    }
}
// 输出结果
// Hello world是否包含 H 和 W:false
// Hello World是否包含 H 和 W:true

3、默认方法 or()

与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”操作。 ​ 这个默认方法接收一个 Predicate 参数,返回一个 Predicate 参数。

JDK 源码为:

/**
     * 组合方法,将当前的谓语与另一个谓语进行短路的或操作,返回一个谓语对象
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

or 方法演示示例:

案例需求:判断一个字符串的长度大于 10 或者小于 5

案例步骤:

  1. 创建三个字符串 s1,s2,s3 内容如下图
  2. 使用 Lambda 创建 2 个 Predicate 接口对象,第 1 个判断长度是否大于 10,每 2 个判断长度是否小于 5
  3. 调用 or 和 test 方法输出每个字符串的测试结果
public class LambdaTest {

    public static void main(String[] args) {
        //创建三个字符串
        String s1 = "Hello World"; //大于 10
        String s2 = "Java"; //小于 5
        String s3 = "I am boy"; //既不大于 10,又不小于 5

        //使用 Lambda 创建 2 个 Predicate 接口对象
        Predicate<String> p1 = s -> s.length() > 10;
        Predicate<String> p2 = s -> s.length() < 5;

        //输出每个字符串的测试结果
        System.out.println(s1 + "=" + p1.or(p2).test(s1));
        System.out.println(s2 + "=" + p1.or(p2).test(s2));
        System.out.println(s3 + "=" + p1.or(p2).test(s3));
    }
}
// 输出结果
// Hello World=true
// Java=true
// I am boy=false

4、默认方法 negate()

“与”、“或”已经了解了,剩下的“非”(取反)也会简单。方法没有参数,返回值为 Predicate。

默认方法 negate的 JDK 源代码为:

/**
     * 对当前的谓语进行逻辑非操作,返回一个谓语对象
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
从实现中很容易看出,它是执行了 test 方法之后,对结果 boolean 值进行“!”取反而已。
​
要在 test 方法调用之前调用 negate 方法,正如 and 和 or 方法一样。

案例需求:判断年龄是否小于 18 岁,将判断的结果取反。

案例步骤

  1. 创建 2 个整数类型的年龄,一个 25,一个 15 岁。
  2. 使用 Lambda 创建 1 个 Predicate,判断年龄小于 18 岁。
  3. 使用 nagate()取反以后再调用 test()方法,输出两个年龄的结果
public class LambdaTest {

    public static void main(String[] args) {
        int age1 = 25; //25 岁
        int age2 = 15; //15 岁

        Predicate<Integer> predicate = (a) -> a < 18; //判断是否小于 18 岁
        System.out.println(age1 + "小于 18 岁,取反:" + predicate.negate().test(age1));
        System.out.println(age2 + "小于 18 岁,取反:" + predicate.negate().test(age2));
    }
}
// 执行结果
// 25小于 18 岁,取反:true
// 15小于 18 岁,取反:false

5、静态方法 isEqual ()

Predicate 中唯一的静态方法,方法的参数是两个 Object 类型,返回一个 Predicate 类型。

​作用:根据 Objects.equals(Object, Object)方法比较两个参数是否相等,

一个对象通过 isEqual()传入,另一个对象通过 test()传入。

// ​java.util.Objects 类中的方法 说明
public static boolean equals(Object a,Object b)

​作用:用于比较两个对象是否相等

​参数:a 和 b 是要比较的两个对象

​返回:如果两个对象相等,则返回 true,否则返回 false

JDK 源代码为:

/**
     * 静态方法,判断 test(object)方法传入的对象是否与参数 targetRef 对象相等
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }

案例需求:比较两个字符串是否相等

案例步骤:

  1. 通过静态方法 isEqual("newboy"),直接返回 Predicate 对象
  2. 调用 Predicate 中的 test()方法传入另两个字符串分别比较
public class LambdaTest {
    public static void main(String[] args) {
        //通过静态方法直接返回 Predicate 对象
        Predicate predicate = Predicate.isEqual("newboy");

        //调用 test()方法传入另两个字符串分别比较
        System.out.println("两个字符串是否相等:" + predicate.test("newboy"));
        System.out.println("两个字符串是否相等:" + predicate.test("NewBoy"));
    }
}
// 执行结果
// 两个字符串是否相等:true
// 两个字符串是否相等:false

6、Predicate 的应用示例

需求说明:

集合当中有多条“姓名+性别”的信息如下:"张飞,男", "貂蝉,女", "曹操,男","孙尚香,女","小乔,女",请通过 Predicate 接口的 and 组合方法,将符合要求的字符串筛选到集合 ArrayList 中,需要同时满足两个条件:

  • 必须为女生
  • 姓名为两个字

开发步骤:

  1. 创建第 1 个 Predicate 判断条件:使用逗号分隔的第 0 个元素姓名长度是 2 
  2. 创建第 2 个 Predicate 判断条件:使用逗号分隔的第 1 个元素性别等于女
  3. 创建一个新的 List 集合,用于存储过滤以后符合条件的字符串
  4. 使用 List 中的 forEach(Lambda)遍历上面的原始 List 集合,使用 Predicate 中的 and 和 test 方法判断每个元素
  5. 两个条件都为真才添加到新的 List 集合中
  6. 创建第 1 个 Consumer 接口,输出使用逗号分隔的第 0 个元素姓名
  7. 创建第 2 个 Consumer 接口,输出使用逗号分隔的第 1 个元素性别
  8. 使用 List 中的 forEach(Lambda)遍历,输出过滤后的新的集合
  9. 使用 Consumer 接口中的 andThen 和 accept 方法,输出每一个元素
public class LambdaTest {
    public static void main(String[] args) {
        //从数组中创建一个 List 集合
        List<String> list = Arrays.asList("张飞,男", "貂蝉,女", "曹操,男","孙尚香,女","小乔,女");

        //创建第 1 个 Predicate 判断条件:使用逗号分隔的第 0 个元素姓名长度是 2
        Predicate<String> pname = s -> s.split(",")[0].length() ==2;
        //创建第 2 个 Predicate 判断条件:使用逗号分隔的第 1 个元素性别等于女
        Predicate<String> pgender = s-> s.split(",")[1].equals("女");

        //创建一个新的 List 集合
        List<String> infos = new ArrayList<>();
        //使用 Lamba 中的 forEach()遍历上面的 List 集合
        //使用 Predicate 中的 and 和 test 方法判断每个元素
        list.forEach(s -> {
            //两个都为真才添加到集合中
            if (pname.and(pgender).test(s)) {
                infos.add(s);
            }
        });
        //创建第 1 个 Consumer 接口,输出使用逗号分隔的第 0 个元素姓名
        Consumer<String> cname = s -> System.out.println("姓名:" + s.split(",")[0]);
        //创建第 2 个 Consumer 接口,输出使用逗号分隔的第 1 个元素性别
        Consumer<String> cgender = s -> System.out.println("性别:" + s.split(",")[1]);
        //使用 Lamba 中的 forEach()遍历,输出过滤后的集合
        infos.forEach(s -> {
            //使用 Consumer 接口中的 andThen 和 accept 方法,每输出一个元素隔一条线
            cname.andThen(cgender).accept(s);
            System.out.println("---------------");
        });
    }
}
// 输出结果
姓名:貂蝉
性别:女
---------------
姓名:小乔
性别:女
---------------

四、Function 接口

Function接口:

        根据一个参数得到另一个参数值,前面称为计算的参数,后面称为计算的结果。

        有进有出,所以称为“函数 Function”。     

类似于数学中的函数,通过一个变量求出另一个变量的值。如:f(x) = 2x+3

以下是它的 Java 源代码:

// 代表通过一个变量求出另一个变量的结果的函数
@FunctionalInterface
public interface Function<T, R> {

    /**
     * 对给定的变量 t 进行计算,得到返回的结果 R
     */
    R apply(T t);

    /**
     * 默认组合方法,先计算当前函数,再计算传入的函数
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * 默认组合方法,先计算传入的函数,再计算当前函数
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
     * 静态方法:总是返回它的输入变量
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

1、抽象方法:apply()

是java.util.function.Function 接口中的方法 

​R apply(T t); 对给定的变量 t 进行计算,得到返回的结果 R

apply 方法演示示例:

案例需求:将 Integer 类型转换为 String 类型,并且输出转换以后字符串的长度。

  1. 创建一个 Function 对象,输入类型是整数,输出类型是字符串
  2. Lambda 表达式将一个整数 i 转成字符串
  3. 调用 apply(数字)方法得到转换后的字符串,再调用字符串的 length()方法得到长度,打印输出
  4. 第 1 次转换 99 这个数字,第 2 次转换 1000 这个数字
public class LambdaTest {
    public static void main(String[] args) {
        //创建一个 Function 对象
        Function<Integer,String> converter = i -> Integer.toString(i);
        System.out.println("99 转成字符串的长度是:" + converter.apply(99).length());
        System.out.println("1000 转成字符串的长度是:" + converter.apply(1000).length());
    }
}
// 输出结果
99 转成字符串的长度是:2
1000 转成字符串的长度是:4

2、默认方法:andThen()

Function 接口中有一个默认的 andThen 方法,用来进行组合操作。 ​

先计算当前函数,再计算传入的函数。两个函数依次执行。 ​

andThen 方法的参数是 Function 对象,返回一个 Function 对象。

JDK 源代码:

    /**
     * 默认组合方法,先计算当前函数,再计算传入的函数
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

案例需求:

连续进行两个操作:第 1 个操作是将字符串转换成为 int 数字,第 2 个操作将转换好的数字乘以 10。两个操作按照前后顺序组合到一起。

  1. 让用户从键盘输入 1 个数字,使用字符串接收。
  2. 创建第 1 个 Function 函数将字符串转成整数
  3. 创建第 2 个函数将整数乘以 10 返回
  4. 调用 andThen 方法和 apply,并且输出结果
public class LambdaTest {
    public static void main(String[] args) {
        //用户输入一个字符串
        System.out.println("请输入数字:");
        Scanner input = new Scanner(System.in);
        String str = input.nextLine();
        //第 1 个函数将字符串转成整数
        Function<String,Integer> f1 = s -> Integer.parseInt(s);
        //第 2 个函数将整数乘以 10 返回
        Function<Integer,Integer>  f2 = i -> i * 10;
        //调用 andThen 方法,并且输出结果
        System.out.println("转成整数并乘以 10 以后的结果是:" + f1.andThen(f2).apply(str));
    }
}
// 输出结果
请输入数字:
2
转成整数并乘以 10 以后的结果是:20

3、默认方法:compose()

Function 中有一个与 andThen 非常类似的 compose 方法。

中文是"组成"的意思,方法参数是 Function,返回值是 Function,先运行参数的 apply 方法,再调用自己的 apply 方法。

其 JDK 源代码为:

    /**
     * 默认组合方法,先计算传入的函数,再计算当前函数
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
// 结合 andThen 方法的 JDK 源码实现进行对比,会发现 compose 方法的参数 Lamda 将会先执行。
所以二者只是先后顺序的不同而已。

compose 方法的演示

案例需求:

创建两个函数对象:1 个将字符串转成大写,1 个将字符串转成小写。分别使用 andThen 和 compose 方法组合调用,查看不同的计算结果。 

开发步骤:

  1. 创建第 1 个 Function,输入输出都是 String 类型,将字符串转成大写。
  2. 创建第 2 个 Function,输入输出都是 String 类型,将字符串转成小写。
  3. 调用第 1 个函数的 apply 方法,并且输出值
  4. 调用第 2 个函数的 apply 方法,并且输出值
  5. 调用 andThen 方法和 apply 方法查看运行结果
  6. 调用 compose 方法和 apply 方法查看运行结果
public class LambdaTest {
    public static void main(String[] args) {
        Function<String, String> f1 = s -> s.toUpperCase();
        Function<String, String> f2 = s -> s.toLowerCase();
        System.out.println("转成大写:" + f1.apply("Hello"));
        System.out.println("转成小写:" + f2.apply("Hello"));
        System.out.println("先转成大写,再转成小写:" + f1.andThen(f2).apply("Hello"));
        System.out.println("先转成小写,再转成大写:" + f1.compose(f2).apply("Hello"));
    }
}
// 执行结果
转成大写:HELLO
转成小写:hello
先转成大写,再转成小写:hello
先转成小写,再转成大写:HELLO

Function 的应用示例

需求说明:请使用 Function 进行函数拼接,按照顺序执行多个函数。

操作依次为:

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

开发步骤:

  1. 创建第 1 个 Function 对象,将字符串 20 取出,返回一个字符串
  2. 创建第 2 个 Function 对象,将字符串转成整数,返回整数
  3. 创建第 3 个 Function 对象,将整数加 100,返回计算结果4) 调用 andThen 方法 2 次,apply 方法应用字符串:"赵丽颖,20",输出结果

代码实现

public class LambdaTest {
    public static void main(String[] args) {
        //创建第 1 个 Function 对象,将字符串 20 取出,返回一个字符串
        Function<String,String> fun1 = s -> s.split(",")[1];
        //创建第 2 个 Function 对象,将字符串转成整数,返回整数
        Function<String,Integer> fun2 = s -> Integer.parseInt(s);
        //创建第 3 个 Function 对象,将整数加 100,返回计算结果
        Function<Integer,Integer> fun3 = num -> num + 100;
        //调用 andThen 方法 2 次,apply 方法应用字符串,输出结果
        System.out.println("计算结果:" + fun1.andThen(fun2).andThen(fun3).apply("赵丽颖,20"));
    }
}
//输出结果
计算结果:120

五、BinaryOperator 接口

BinaryOperator 表示对两个相同类型的操作数进行操作,产生相同类型的结果。

接口中的方法

static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) 
返回一个BinaryOperator ,它根据指定的Comparator返回两个元素中的较大Comparator 。  
​
static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) 
返回BinaryOperator返回根据指定的两个元件的较小的Comparator 。  
​
这个接口中定义了两个静态方法,
BiFunction 是用于定义两个操作符的函数接口。

BiFunction接口中的方法

T apply(T t, T u); 从父接口 BiFunction 中继承下来的抽象方法,
传入两个参数 t 和 u 进行函数计算,返回计算的结果。​
两个参数和返回值都是同一种类型。
​
default <V> BiFunction<T,U,V> andThen(Function<? super R,? extends V> after)
返回一个组合函数,首先将该函数应用于其输入,然后将after函数应用于结果。 
如果任一函数的评估引发异常,则将其转发给组合函数的调用者。 

1、方法的演示:apply()

案例需求:

使用 BinaryOperator 接口的 apply()方法,计算 2 个整数的和,并且输出结果。

案例步骤:

  1. 创建类 Demo25BinaryOperator
  2. 创建 BinaryOperator 接口,使用 Lambda 实现方法,方法有两个参数,返回方法的计算结果。
  3. 调用 apply()方法传入实际的参数,打印计算结果。

案例代码:

public class LambdaTest {
    public static void main(String[] args) {
        BinaryOperator<Integer> operator = (m, n) -> m + n;
        System.out.println("计算结果是:" + operator.apply(3, 5));
    }
}
// 输出结果
计算结果是:8

静态方法

ublic static  BinaryOperator minBy(Comparator comparator)
通过后面的 Comparator 比较器判断,返回两个元素中较小的元素
​
public static  BinaryOperator maxBy(Comparator comparator)
通过后面的 Comparator 比较器判断,返回两个元素中较大的元素
​
Comparator 接口中的静态方法说明
naturalOrder() 按元素的自然排序的大小进行比较,返回一个 Comparator 对象
reverseOrder() 按元素的倒序大小进行比较,返回一个 Comparator 对象

2、BinaryOperator 接口做为方法参数

案例需求:

有如下数组{2,1,3,5},对数组中的每个元素进行替换。替换算法如下:

  1. 第 0 个元素不变
  2. 第 0 个+第 1 个元素的结果代替第 1 个元素
  3. 第 1 个新元素+第 2 个元素的结果代替 2 个
  4. 第 2 个新元素+第 3 个元素的结果代替第 3 个
  5. 依次类推,直到所有的元素替换完成为止。
//Arrays 类中的方法说明
void parallelPrefix(T[] array, BinaryOperator op)

作用:对数组中每个元素使用指定的二元操作函数进行替换操作,并行累积
参数 1:要替换的数组
参数 2:指定二元操作函数

案例步骤

  1. 创建 BinaryOperator对象,指定 2 个数的算法是 m+n
  2. 创建 Integer 类型的数组:{2,1,3,5}
  3. 输出操作前的数组
  4. 调用上面的 parallelPrefix()方法,将 BinaryOperator 做为参数传入
  5. 输出操作后的数组
  6. 如果使用不同的算法,则每个元素的替换的结果不同。如:换成两个数相乘。

案例代码

public class LambdaTest {
    public static void main(String[] args) {
        BinaryOperator<Integer> operator = (m,n) -> m+n;
        Integer [] arr = {2,1,3,5};
        System.out.println("操作前的数组:" + Arrays.toString(arr)) ;
        Arrays.parallelPrefix(arr,operator);
        System.out.println("操作后的数组:" + Arrays.toString(arr)) ;
    }
}
//输出结果
操作前的数组:[2, 1, 3, 5]
操作后的数组:[2, 3, 6, 11]

3、静态方法的演示

案例需求:

比较两个整数,使用 minBy 静态方法找出最小值比较两个字符串,使用 maxBy 静态方法找出最大值

案例步骤

  1. 创建 BinaryOperator对象,使用 minBy()静态方法,按数字的正常大小进行比较。
  2. 输出最小值,调用 apply()方法,传入 2 个整数。
  3. 创建 BinaryOperator对象,使用 maxBy()静态方法,按字符串的大小进行比较。
  4. 输出最大值,调用 apply()方法,传入 2 个字符串:"ABCD","xyz"

案例代码

public class LambdaTest {
    public static void main(String[] args) {
        //naturalOrder()是 Comparator 中的静态方法,即按数字的正常大小进行比较
        BinaryOperator oper1 = BinaryOperator.minBy(Comparator.naturalOrder());
        System.out.println("最小值是:" + oper1.apply(3,5));

        //naturalOrder()是 Comparator 中的静态方法,即按字符串的正常大小进行比较
        BinaryOperator oper2 = BinaryOperator.maxBy(Comparator.naturalOrder());
        System.out.println("最大值是:" + oper2.apply("ABCD","xyz"));
    }
}
// 输出结果
最小值是:3
最大值是:xyz

六、UnaryOperator 接口

UnaryOperator 表示对单个操作数的操作,该操作数生成与其操作数类型相同的结果。

UnaryOperator 接口继承于 Function 接口,

所以有 T apply(T t)抽象方法,与前面的 Function 接口中的 apply()方法相同。

它的输入类型和返回类型是相同的类型。

UnaryOperator 接口的源码

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    /**
     * 始终返回其输入参数的一元运算符
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

方法的演示

UnaryOperator 接口中的方法说明
​
T apply(T t); 
    从 Function 接口中继承下来的抽象方法,使用给定的参数应用此一元运算函数,返回另一个值。
    参数和返回值是同一种类型。
​
static  UnaryOperator identity() 
    始终返回其输入参数的一元运算符也就是后续 apply()输入的是什么,就返回什么。

案例步骤

  1. 使用 UnaryOperator.identity()静态方法创建 UnaryOperator对象
  2. 应用 apply()方法,输入字符串 abc,得到结果也是 abc。
public class LambdaTest {
    public static void main(String[] args) {
        //创建一个 UnaryOperator对象,
        UnaryOperator operator = UnaryOperator.identity();

        //调用 apply()方法,输出参数的值
        System.out.println("输出与输入一样:" + operator.apply("abc"));
    }
}
//输出结果
输出与输入一样:abc

UnaryOperator 使用方法的参数

案例需求:

有一个整数的列表集合,将集合中每个元素乘以 2,再替换这个元素,输出替换前后的列表集合有一个字符串的列表集合,将集合中每个元素用它的大写进行替换。

ArrayList 中的方法说明
replaceAll(UnaryOperator operator) 
使用一元操作函数的结果,替换列表中的每个元素

案例步骤:

  1. 使用 Arrays.asList()创建一个整数列表
  2. 创建 UnaryOperator一元运算函数,指定运算表达式是 x*2
  3. 调用 ArrayList 的 replaceAll()方法,把上面创建的一元运算函数做为参数传入
  4. 输出替换前后的列表
  5. 使用 Arrays.asList()创建一个字符串列表
  6. 这次直接在 replaceAll()方法中传入 Lambda 表达式,s.toUpperCase()
  7. 输出替换前后的列表
public class LambdaTest {
    public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(3, 10, 8, 2);
        System.out.println("替换前:" + nums);
        UnaryOperator<Integer> oper = x -> x * 2;
        nums.replaceAll(oper);
        System.out.println("替换后:" + nums);

        List<String> names = Arrays.asList("Jack","Rose","Tom","NewBoy");
        System.out.println("替换前:" + names);
        names.replaceAll(s -> s.toUpperCase());
        System.out.println("替换后:" + names);
    }
}
//输出
替换前:[3, 10, 8, 2]
替换后:[6, 20, 16, 4]
替换前:[Jack, Rose, Tom, NewBoy]
替换后:[JACK, ROSE, TOM, NEWBOY]

七、常用的函数式接口小结

(1)Supplier 提供数据者 

  1. T get();没有传入参数,有结果。

(2)Consumer 消费数据者 

  1. void accept(T t); 传入数据,没有结果。 
  2. andThen()   

(3)Predicate 谓语 

  1. boolean test(T t); 对传入的数据逻辑判断 
  2. and()
  3. or()
  4. negate()
  5. isEqual()

(4)Function 函数 

  1. R apply(T t); 传入一个变量返回计算结果 
  2. andThen()
  3. compose()
  4. identity()

(5)BinaryOperator 二元操作符 

  1. T apply(T t,T u); 传入两个参数返回一个结果 
  2. andThen()
  3. 继承于 BiFunction

(6)UnaryOperator

  1. 继承于 Function
  2. 一元操作符 
  3. T apply(T t); 传入一个参数返回一个结果 
  4. andThen()
  5. compose()
  6. identity()

参考:

Java 8 函数式接口

https://www.runoob.com/java/java8-functional-interfaces.html

死磕函数式接口——常用函数式接口

https://blog.csdn.net/heihaozi/article/details/104257942?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.control&spm=1001.2101.3001.4242

常用函数式接口

https://blog.csdn.net/h294590501/article/details/80303693

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值