常用函数式接口

2023-11-01

常用函数式接口

JDK 8 中重要的函数接口

接口      参数          返回          中文          示例
Supplier None T 提供者 工厂方法创建对象

Consumer T void 消费者 输出一个值

Predicate T boolean 谓语,顾名思义,中文中的‘是’与‘不是’是中文语法的谓语

Function T R 函数 获得某个对象的名字

BinaryOperator (T, T) T 二元操作符,二元(就是数学里二元一次方程那个二元,代表 2 个的意思),双重的。
                即有两个操作数 例如求两个数的乘积(*)
    
UnaryOperator T T 一元操作符,只有一个操作数 逻辑非(!)

Supplier 接口

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

Supplier 接口是用来生成数据的,数据的类型通过泛型参数给定。

使用 get()方法获得返回值,不要求每次调用时都返回新的或不同的结果。

接口的源码如下:

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

Supplier 接口中的方法

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

将 Supplier 直接使用

 案例需求get()方法的基本使用,在 Supplier 接口中 get 方法返回一个字符串。 案例步骤:1) 使用 Lambda 创建 Supplier 对象,泛型类型为 String,方法体返回一个字符串,return 可以省略。2) 调用 Supplier 的 get()方法得到字符串,并打印输出

public class DemoSupplier {

 public static void main(String[] args) {

 //使用 Lambda 创建 Supplier 对象

 Supplier supplier = () -> "Hello Java!";

 //输出它的值

 System.out.println(supplier.get());

 }
}

将 Supplier 使用生成对象

 案例需求:

下面的例子演示了如何通过调用一个静态方法,生成一个员工对象返回。

使用构造方法做为 Supplier 参数的引用。

 案例步骤:1) 在主类内部创建一个私有的静态 Employee 对象,重写 toString()方法,返回一个字符串:"我是员工"。

2) 在 main 函数中创建一个 Supplier 对象,泛型类型是 Employee。使用 Lambda 传入 Supplier 对象,方法体实例化员工对象,省略 return 方法。

3) 使用 supplier 对象的 get()方法得到员工对象

4) 打印输出员工对象

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

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

//主类
public class DemoSupplier {

 //员工类
 private static class Employee {//注意static

 @Override

 public String toString() {

 return "我是员工";

 }

 }

 public static void main(String[] args) {

 //使用 Lambda 传入 Supplier 对象,将生成一个员工对象//此时仅仅是实例化了接口并未执行里面代码

 Supplier supplier = ()->new Employee();

 //输出员工对象

 System.out.println(supplier.get());

 }
}

将 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 DemoSupplier {
 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();
 }
}

Consumer 接口

Consumer 接口代表接受单一的输入变量而且没有返回值的一类操作。

它的作用和 Supplier 相反,是消费一个数据的,消费的数据类型需要通过泛型指定。

它的源代码如下:


@FunctionalInterface
public interface Consumer<T>{
 //接受 t 对象,无返回值
 void accept(T t);
    
//默认的组合方法,参数和返回值都是 Consumer 类型,先调用自己的 accept()方法,再调用参数的 accept()方法
 default Consumer<T>  andThen(Consumer<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。

直接使用 Consumer 对象

 实现步骤:1) 使用 Lambda 创建 Consumer 对象,直接打印传入的字符串数据。2) 调用 Consumer 的 accept()方法,在 accept()方法中传入一个字符串数据。

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

Consumer 做为参数
List  Set 集合中遍历的 forEach 方法它的参数就是 Consumer,

请看下面的代码: 案例需求:1) 创建一个数组,使用 Arrays.asList("孙悟空", "猪八戒", "白骨精", "嫦娥") 转成 List 对象。2) 使用 forEach 方法打印每一个元素,forEach 中使用 Lamba 表达式输出传入的字符串

public class DemoForEach {
 public static void main(String[] args) {
 //将数组转成 List 对象
 List names = Arrays.asList("孙悟空", "猪八戒", "白骨精", "嫦娥");
 //打印每一个字符串,forEach 的参数就是 Consumer
 names.forEach(t -> System.out.println(t));
 }
}

分析 forEach()方法的源代码
default void forEach(Consumer<T> action) {
 Objects.requireNonNull(action);//判断某一对象是否不为null
 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) {
 Objects.requireNonNull(after); //判断 after 是否为 null
    
//先调用自己的 accept()方法,再调用参数的 accept()方法
 return (T t) -> { accept(t); after.accept(t); };
}

要想实现组合,需要两个或多个 Lambda 表达式,而 andThen 的语义正是执行“一步接一步”操作。

andThen 方法演示示例 案例需求:将字符串 Hello 首先打印大写的 HELLO,然后打印小写的 hello 实现步骤:1) 创建 Consumer 对象 c1,使用 Lambda 打印 s 对象的大写2) 创建 Consumer 对象 c2,使用 Lambda 打印 s 对象的小写3) c1 调用 andThen(c2)方法,再调用 accept("字符串"),完成依次的操作。

public class DemoConsumer {
    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");
    }
}

使用 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 DemoConsumerPrint {
    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接口。

Predicate 的源码:

@FunctionalInterface
public interface Predicate<T> {
 boolean test(T t); //抽象方法,对 t 进行测试,返回 boolean 类型

/**
- 组合方法,将当前的谓语与另一个谓语进行短路的与操作,返回一个谓语对象
  */
   default Predicate<T> and(Predicate<? super T>  other) {
   Objects.requireNonNull(other); //判断 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  Predicate<T> isEqual(Object targetRef) {
   return (null == targetRef)
   ? Objects::isNull
   : object -> targetRef.equals(object); /
   }
  }

Predicate 接口中的方法

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

 案例需求:判断 test("字符串")方法给定的参数长度是否大于 5 案例步骤:1) 创建一个 Predicate 谓语对象,使用 Lambda 实现 boolean test(T t)方法2) 方法体的参数是 s,返回字符串的长度大于 5,省略 return 关键字。3) 两次调用 test()方法看运行结果,第 1 次使用字符串 Hello,第 2 次使用字符串 Predicate

public class DemoPredicateTest {

 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"));

 }
}
默认方法 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 是否包含 H4) 判断字符串 s 是否包含 W5) 调用 and 方法和 test 方法,分别输出 s1 和 s2 的结果

public class DemoPredicateAnd {

 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));

 }
}

默认方法 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 个判断长度是否小于 53) 调用 or 和 test 方法输出每个字符串的测试结果

public class DemoPredicateOr {

 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));

 }
}

默认方法 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 DemoPredicateNegate {

 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));

 }
}

静态方法 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 DemoPredicateIsEqual {

 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"));

 }

}

Predicate 的应用示例

 需求说明集合当中有多条“姓名+性别”的信息如下:"张飞,男", "貂蝉,女", "曹操,男","孙尚香,女","小乔,女"

请通过 Predicate 接口的 and 组合方法,将符合要求的字符串筛选到集合 ArrayList 中,

需要同时满足两个条件:1) 必须为女生 2) 姓名为两个字

 开发步骤: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 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 源代码

import java.util.Objects;

/**
代表通过一个变量求出另一个变量的结果的函数

@param  输入给函数的变量

@param  函数输出的结果
*/
@FunctionalInterface
public interface Function<T, R> {

 /**
对给定的变量 t 进行计算,得到返回的结果 R
*/
 R apply(T t);
  
    
 /**
默认组合方法,先计算当前函数,再计算传入的函数
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}
    
 /**
默认组合方法,先计算传入的函数,再计算当前函数
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    Objects.requireNonNull(before);
    return (V v) -> apply(before.apply(v));
}

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

 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());
 }
}

默认方法: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));
}

andThen 方法演示示例: 案例需求:连续进行两个操作:第 1 个操作是将字符串转换成为 int 数字,第 2 个操作将转换好的数字乘以 10。两个操作按照前后顺序组合到一起。1) 让用户从键盘输入 1 个数字,使用字符串接收。2) 创建第 1 个 Function 函数将字符串转成整数3) 创建第 2 个函数将整数乘以 10 返回4) 调用 andThen 方法和 apply,并且输出结果

public class DemoFunctionAndThen {
    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));
    }
}

默认方法: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 DemoFunctionCompose {
    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"));
    }
}

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 DemoFunctionApp {
    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"));
    }
}

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 是用于定义两个操作符的函数接口。

BinaryOperator 接口中的方法
T apply(T t, T u); 从父接口 BiFunction 中继承下来的抽象方法,
传入两个参数 t  u 进行函数计算,返回计算的结果。

两个参数和返回值都是同一种类型。


default <V> BiFunction<T,U,V> andThen(Function<? super R,? extends V> after)
返回一个组合函数,首先将该函数应用于其输入,然后将after函数应用于结果。 
如果任一函数的评估引发异常,则将其转发给组合函数的调用者。 

参数类型 
V - after函数的输出类型,以及组合函数 
参数 
after - 应用此函数后应用的功能 
结果 
一个组合函数首先应用此函数,然后应用 after函数 

方法的演示:apply()

 案例需求:使用 BinaryOperator 接口的 apply()方法,计算 2 个整数的和,并且输出结果。 案例步骤:1) 创建类 Demo25BinaryOperator2) 创建 BinaryOperator 接口,使用 Lambda 实现方法,方法有两个参数,返回方法的计算结果。3) 调用 apply()方法传入实际的参数,打印计算结果。 案例代码:

public class DemoBinaryOperator {
    public static void main(String[] args) {

        BinaryOperator<Integer> operator = (m, n) -> m + n;

        System.out.println("计算结果是:" + operator.apply(3, 5));
    }
}

静态方法

public static  BinaryOperator minBy(Comparator comparator)
通过后面的 Comparator 比较器判断,返回两个元素中较小的元素

public static  BinaryOperator maxBy(Comparator comparator)
通过后面的 Comparator 比较器判断,返回两个元素中较大的元素

Comparator 接口中的静态方法说明
naturalOrder() 按元素的自然排序的大小进行比较,返回一个 Comparator 对象
reverseOrder() 按元素的倒序大小进行比较,返回一个 Comparator 对象

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+n2) 创建 Integer 类型的数组:{2,1,3,5}3) 输出操作前的数组4) 调用上面的 parallelPrefix()方法,将 BinaryOperator 做为参数传入5) 输出操作后的数组6) 如果使用不同的算法,则每个元素的替换的结果不同。如:换成两个数相乘。 案例代码

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)) ;
}

静态方法的演示 案例需求比较两个整数,使用 minBy 静态方法找出最小值比较两个字符串,使用 maxBy 静态方法找出最大值 案例步骤1) 创建 BinaryOperator对象,使用 minBy()静态方法,按数字的正常大小进行比较。2) 输出最小值,调用 apply()方法,传入 2 个整数。3) 创建 BinaryOperator对象,使用 maxBy()静态方法,按字符串的大小进行比较。4) 输出最大值,调用 apply()方法,传入 2 个字符串:"ABCD","xyz" 案例代码

 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"));
}

UnaryOperator 接口

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

UnaryOperator 接口继承于 Function 接口,

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

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

UnaryOperator 接口的源码

package java.util.function;
@FunctionalInterface
public interface UnaryOperator extends Function {
 /**
始终返回其输入参数的一元运算符
*/
 static  UnaryOperator identity() {
 return t -> t;
 }
}

方法的演示

UnaryOperator 接口中的方法 说明

T apply(T t); 
     Function 接口中继承下来的抽象方法,使用给定的参数应用此一元运算函数,返回另一个值。
    参数和返回值是同一种类型。

static  UnaryOperator identity() 
    始终返回其输入参数的一元运算符也就是后续 apply()输入的是什么,就返回什么。

 案例步骤1) 使用 UnaryOperator.identity()静态方法创建 UnaryOperator对象2) 应用 apply()方法,输入字符串 abc,得到结果也是 abc。 案例代码

 public static void main(String[] args) {
 //创建一个 UnaryOperator对象,
 UnaryOperator operator = UnaryOperator.identity();
     
 //调用 apply()方法,输出参数的值
 System.out.println("输出与输出一样:" + operator.apply("abc"));
}

UnaryOperator 使用方法的参数

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

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

1) 使用 Arrays.asList()创建一个整数列表2) 创建 UnaryOperator一元运算函数,指定运算表达式是 x*23) 调用 ArrayList 的 replaceAll()方法,把上面创建的一元运算函数做为参数传入4) 输出替换前后的列表5) 使用 Arrays.asList()创建一个字符串列表6) 这次直接在 replaceAll()方法中传入 Lambda 表达式,s.toUpperCase()7) 输出替换前后的列表 案例代码:

 public static void main(String[] args) {
 List nums = Arrays.asList(3, 10, 8, 2);
 System.out.println("替换前:" + nums);
 UnaryOperator oper = x -> x * 2;
 nums.replaceAll(oper);
 System.out.println("替换后:" + nums);
     
 List names = Arrays.asList("Jack","Rose","Tom","NewBoy");
 System.out.println("替换前:" + names);
 names.replaceAll(s -> s.toUpperCase());
 System.out.println("替换后:" + names);
}

常用的函数式接口小结

Supplier 提供数据者 
    T get();没有传入参数,有结果。

Consumer 消费数据者 
    void accept(T t); 传入数据,没有结果。 
    andThen()
    
Predicate 谓语 
    boolean test(T t); 对传入的数据逻辑判断 
    and()
    or()
    negate()
    isEqual()
    
Function 函数 
    R apply(T t); 传入一个变量返回计算结果 
    andThen()
    compose()
    identity()
    
    
BinaryOperator 二元操作符 
    T apply(T t,T u); 传入两个参数返回一个结果 
    andThen()
    继承于 BiFunction
    
UnaryOperator
    继承于 Function
    一元操作符 
    T apply(T t); 传入一个参数返回一个结果 
    andThen()
    compose()
    identity()

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

常用函数式接口 的相关文章

随机推荐

  • DCN和DCNv2(可变性卷积)学习笔记(原理代码实现方式)

    DCN和DCNv2 可变性卷积 网上关于两篇文章的详细描述已经很多了 我这里具体的细节就不多讲了 只说一下其中实现起来比较困惑的点 黑体字会讲解 DCNv1解决的问题就是我们常规的图像增强 仿射变换 线性变换加平移 不能解决的多种形式目标变
  • Java的mkdir()与mkdirs()引发的悲剧---关于java的mkdir()方法无法创建文件目录问题

    昨晚深夜在做项目的文件上传 在上传之前要先判断指定的文件目录是否存在 如果不存在就先创建改目录 因为之前已经做过类似的功能了 所以就把判断文件目录以及创建的代码直接copy过来了 然而很郁闷的是 一模一样的代码 这回却遇到一个特别奇葩的问题
  • 【python】CliffWalking悬崖寻路问题

    强化学习 简介 gym库 CliffWalking SARSA Q learning 示例 SARSA Q learning 简介 机器学习 监督学习 非监督学习 强化学习 模仿人类和动物的试错机制进行学习 智能体与环境交互 根据当前的环境
  • 数据结构面试常见问题总结

    数据结构面试常见问题总结 写在前面 本文记录了一些数据结构面试常见问题 本意用于考研复试 以下面试题为网上整理的问题以及自己加入的一些问题 答案仅供参考 Q 数据结构三要素 A 逻辑结构 物理结构 数据运算 Q 数组与链表有什么区别 A 数
  • innovus中常用命令整理

    restoreDesign load 之前的db list property type 列举出相应的属性 get property 得到相应的object的属性 get pin 获取pin get port 获取port ecoRoute
  • 2023开学礼山东财经大学《乡村振兴战略下传统村落文化旅游设计》许少辉新财经图书馆

    2023开学礼山东财经大学 乡村振兴战略下传统村落文化旅游设计 许少辉新财经图书馆
  • adworld攻防世界 reverse asong

    asong 攻防世界 reverse 进阶区 asong 题目文件 https www jianguoyun com p DQ3g5b4QiNbmBxjX fQC 访问密码 AgV9Sh 主要是集中我们常见的处理方式的整合 注意一个对于ou
  • 进程间通信---管道通信

    进程间通信为什么有那么多不同的方法 资源的不同 所以通信的方式不同 想要获取管道资源 就需要用管道来通信 想要获取消息队列资源 就需要用消息队列来通信 如上所示 一个进程就是一个PCB PCB中的file struct有三个默认文件描述符
  • 【转】 如何提高自己的acm个人能力

    转载自 简单de数字 最终编辑 fading code by zfy0701 本来以为HNU的huicpc035和我一样退役了 后来听说他组成了新的footman队 于是又关注了下他 035体现了两个我觉得非常重要的品质 1 刻苦的训练 2
  • vmtools的安装和使用

    介绍 vmtools工具是在虚拟系统和主机系统进行共享文件夹的工具 1 用root用户登录CentOS后删除桌面的光驱 2 点击菜单栏的虚拟机 gt 安装VMwareTools 3 安装结果如下所示 4 打开VMwareTools 复制VM
  • 【python】Something is wrong with the numpy installation

    2020年2月5日 0次阅读 共448个字 0条评论 0人点赞 QueenDekimZ COCO API windows下安装COCO API时 python setup py build ext install 出现报错 ImportEr
  • Unity中UI框架的使用3-主界面中的弹窗和关闭

    效果图 在主页面点击排位赛按钮 就会弹出图2中的一个弹窗 再点击弹窗右上角的关闭按钮 就会关闭弹窗 回到图3的效果 方法 1 将PopUp这个面板添加到UIPanelType cs文件中 并且将其名称和路径添加到UIPanelType js
  • Python高级函数1:使用 map()、reduce()、filter()、zip() 和 enumerate() 简化代码

    Python高级函数1 使用 map reduce filter zip和 enumerate 简化代码 1 原理 1 1 map 函数 1 2 reduce 函数 1 3 filter 函数 1 4 zip 函数 1 5 enumerat
  • 在分布式环境下标准支付流程的梳理

    支付流程图的梳理 https www processon com diagraming 61a18a895653bb136f893ecc 提交订单 当用户点击立即购买或者提交订单的这个时候数据库就会记录一笔订单 此项业务主要是用到了rabb
  • Android 设置ListView不可滚动 及在ScrollView中不可滚动的设置

    转载请注明出处 http blog csdn net androiddevelop article details 38815493 希望得到的效果是ListView不能滚动 但是最大的问题在与ListView Item还必有点击事件 如果
  • 2023华为OD机试真题【区间交叠/贪心算法】【Python Java C++】

    题目描述 给定坐标轴上的一组线段 线段的起点和终点均为整数并且长度不小于1 请你从中找到最少数量的线段 这些线段可以覆盖住所有线段 输入描述 第一行输入为所有线段的数量 不超过10000 后面每行表示一条线段 格式为 x y x和y 分别表
  • vscode因网络下载失败的问题

    复制出失败的下载链接 https az764295 vo msecnd net stable d045a5eda657f4d7b676dedbfa7aab8207f8a075 VSCodeUserSetup x64 1 72 2 exe 将
  • 多任务视频推荐方案,百度工程师实战经验分享

    推荐系统的应用场景非常广泛 比如非常火爆的短视频推荐 电商平台商品推荐 搜索推荐等 但是你知道吗 短视频APP在向你展示一个你感兴趣的视频之前 通常既要预测你对这个视频是否感兴趣 又要预测你会看多久 点赞还是不点赞 会不会偷偷收藏起来下次接
  • 迁移学习matlab

    迁移学习是一种机器学习技术 它可以利用已有的模型和数据来加速新模型的训练 在Matlab中实现迁移学习 需要先选定一个预训练的模型 然后使用该模型的权重来初始化新模型 最后对新模型进行微调以适应特定的任务 Matlab中有一些已经预先训练好
  • 常用函数式接口

    常用函数式接口 JDK 8 中重要的函数接口 接口 参数 返回 中文 示例 Supplier None T 提供者 工厂方法创建对象 Consumer T void 消费者 输出一个值 Predicate T boolean 谓语 顾名思义