Java之Lambda表达式和函数式接口(常用接口:Supplier、Consumer、Predicate、Function)

目录

Lambda表达式

Lambda表达式的样例

Lambda的使用前提

函数式接口

函数式编程

常用函数式接口

1.Supplier接口

2.Consumer接口

3.Predicate接口

4.Function接口


编程思想

面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。
函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。

JDK1.8+中加入了lambda表达式!

// 面向对象的编程思想和函数式编程思想的比较
public class ObjectAndFunction {
    public static void main(String[] args) {
        // 匿名内部类(面向对象编程)
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("面向对象式的任务执行");
            }
        }).start();
        // 使用lambda表达式(函数式编程)
        new Thread(()-> System.out.println("面向函数式的任务执行")).start();
    }
}

匿名内部类(面向对象的编程方式)的冗余体现:

  • Thread 类需要Runnable 接口作为参数,其中的抽象run 方法是用来指定线程任务内容的核心;
  • 为了指定run 的方法体,不得不需要Runnable 接口的实现类;
  • 为了省去定义一个RunnableImpl 实现类的麻烦,不得不使用匿名内部类;
  • 必须覆盖重写抽象run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
  • 而实际上,似乎只有方法体才是关键所在。

相比起来,lambda表达式(函数式编程)的更加简洁,不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担。

Lambda表达式

Lambda省去面向对象的条条框框,格式由3个部分组成:一些参数、一个箭头、一段代码。

表达式的标准格式:

(参数类型  参数名称) -> {代码语句}

  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
  • -> 是新引入的语法格式,代表指向动作。
  • 大括号内的语法与传统方法体要求基本一致。

表达式的省略格式:

  1. 小括号内参数的类型可以省略;(总是可以)
  2. 如果小括号内有且仅有一个参,则小括号可以省略;(仅有一个)
  3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。(仅有一句)

Lambda表达式的样例

1.无参无返回

给定一个厨子接口Cook,包含一个抽象方法makeFood,这个抽象方法没有参数也没有返回值。需求:在下面的代码中请使用Lambda表达式调用invokeCook方法打印输出“我要吃饭!”。

// 题目中给出的Cook接口
public interface Cook{
    public abstract void makeFood();
}
// 题目中给出的测试类,并且里面设置了填空,需要填写lambda表达式
public class InvokeCook{
    public static void main(String[] args){
        // 此处填写lambda表达式
    }
    public static void invokeCook(Cook cook){
        cook.makeFood();
    }
}

解答:使用lambda调用invokeCook,小括号代表参数为空,表示的是Cook 接口makeFood 抽象方法的参数,大括号代代码体代表makeFood 的方法体。

public static void main(String[] args) {
    // 无参数无返回值的面向对象式编程
    invokeCook(new Cook() {
        @Override
        public void makeFood() {
            System.out.println("面向对象式的使用");
        }
    });
    // 无参数无返回值的函数式编程
    invokeCook(()-> {System.out.println("函数式lambda表达式标准格式的使用");});
    invokeCook(()-> System.out.println("函数式lambda表达式省略格式的使用"));
}

2.有参有返回

需求:使用数组存储多个Person对象,对数组中的Person对象使用Arrays数组工具类的sort方法通过年龄进行升序排列。

解答:下面是使用lambda的表达式(标准格式和省略格式)和面向对象的方式对比。

// 定义一个Person类,实现Comparable接口并重写compareTo方法
public class Person implements Comparable {
    private String name;
    private int age;
    private int nice;  // 美貌值 0-10

    @Override  // 要使用默认的sort规则排序,就必须重写Comparable接口中的compareTo方法。
    public int compareTo(Person o) {
//        return 0; //原来的代码恒返回0,表示不需要交换未知也就是不进行排序返回原序列。
        if (this.age > o.age) {  // 只对年龄进行了排序
            return 1;  // 返回正序。原来的顺序:this.age是当前元素,o.age是下一个元素。return 1需要交换位置。
        }
        if (this.age < o.age) {
            return -1;
        }
        return 0;
        // 等效于下面这句
//        return this.age-o.age;  // this.age永远是当前的元素,o.age是下一个元素。当为正的时候,即return 1.需要交换顺序。其余情况则不需要交换顺序。
        /* 当 this.age>o.age时,年龄按升序排序。
           所以return this.age-o.age时,年龄升序排序。
           当 this.age(){
                @Override
                public int compare(Integer o1, Integer o2) {
                    return 0;
                }
            }
        );
        System.out.println("从大到小:"+Arrays.toString(array)); // 从大到小:[23, 11, 10, 5]

        // array数组中的元素是自定义类型/对象
        Person[] persons = {new Person("赵丽颖",20,7),new Person("迪丽热巴",18,9),new Person("古力娜扎",19,8)};
        // 要使用默认规则排序,被排序的array数组里面存储的元素,必须实现Comparable接口,重写接口中的compareTo方法定义排序的规则。
        Arrays.sort(persons); // 进行排序,排序规则已经重写,只对年龄排序,是升序排序。
        System.out.println("面向对象,只对年龄升序排序:"+Arrays.toString(persons)); // 面向对象,只对年龄升序排序:[Person{name='迪丽热巴', age=18, nice=9}, Person{name='古力娜扎', age=19, nice=8}, Person{name='赵丽颖', age=20, nice=7}]
        // 面向对象编程。使用第三方的,不需要去继承Comparable类和重写compareTo方法,只需要传入Comparator对象和重写compare方法。
        Arrays.sort(persons, new Comparator() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getNice()-o2.getNice();// 美貌值从小到大排序
            }
        });
        System.out.println("面向对象,只对美貌值升序排序:"+Arrays.toString(persons)); // 面向对象,只对美貌值升序排序:[Person{name='赵丽颖', age=20, nice=7}, Person{name='古力娜扎', age=19, nice=8}, Person{name='迪丽热巴', age=18, nice=9}]

        // 函数式编程,使用lambda表达式的标准格式
        Arrays.sort(persons,(Person o1,Person o2)->{return o2.getAge()-o1.getAge();});
        System.out.println("lambda的标准格式,只对美貌值降序排序:"+Arrays.toString(persons)); // lambda的标准格式,只对美貌值降序排序:[Person{name='赵丽颖', age=20, nice=7}, Person{name='古力娜扎', age=19, nice=8}, Person{name='迪丽热巴', age=18, nice=9}]
        // 相比于标准格,(1)小括号内参数的类型可以省略(2)大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
        Arrays.sort(persons,(o1,o2)->o1.getAge()-o2.getAge());
        System.out.println("lambda的省略格式,只对美貌值升序排序:"+Arrays.toString(persons)); // lambda的省略格式,只对美貌值升序排序:[Person{name='迪丽热巴', age=18, nice=9}, Person{name='古力娜扎', age=19, nice=8}, Person{name='赵丽颖', age=20, nice=7}]
    }
}

Lambda的使用前提

有且仅有一个抽象方法的接口,称为“函数式接口”。

1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
     备注:无论是JDK内置的Runnable 、Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
2. 使用Lambda必须具有上下文推断。
      备注:也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

函数式接口

概念:函数式接口在Java中是指:有且仅有一个抽象方法的接口。

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导(也是Lambda使用前提)。

“语法糖”:指的是使用更加方便,但是原理不变的代码语法。

举例:for-each语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。

函数式接口的格式:

只要确保接口中有且仅有一个抽象方法即可:其中抽象方法中的public abstract这两个关键字是可以省略的。

修饰符 interface 接口名称 {
       public abstract 返回值类型 方法名称(可选参数信息);
       // 其他非抽象方法内容
}

@FunctionalInterface注解(JDK1.8+):

与@Override 注解的作用类似(保险栓),Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

函数式接口的典型使用场景就是作为方法的参数

// 接口定义
@FunctionalInterface  // 函数式接口加保险栓,编译检查
public interface MyInterface {
    public abstract void method();
}
// 接口实现类的定义
public class MyInterfaceImpl implements MyInterface{
    @Override  // 保险栓
    public void method() {
        System.out.println("使用实现类重写接口中的抽象方法");
    }
}
// 函数式接口的使用
public class MyInterfaceFunction {
    public static void main(String[] args) {
        // (1)方法的参数是一个接口,所以可以传递接口的实现类对象
        show(new MyInterfaceImpl());
        // (2)调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
        show(new MyInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口中的抽象方法");
            }
        });
        // (3)方法的参数是一个函数式接口,所以可以使用Lambda表达式
        show(()-> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
    }
    
    // 函数式接口的使用:一般可以作为方法的参数和返回值类型
    public static void show(MyInterface myInter){
        myInter.method();
    }
}

函数式编程

在兼顾面向对象特性的基础上,Java语言通过Lambda表达式与方法引用等,为开发者打开了函数式编程的大门。

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式(函数式编程)是延迟执行的,这正好可以作为解决方案,提升性能。

性能浪费的例子:

日志案例。日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。

public class Logger {
    public static void main(String[] args) {
        String msgA = "迪丽热巴";
        String msgB = "中国女性新力量";
        String msgC = "优秀的人";
        log(1,msgA+msgB+msgC);
    }
    public static void log(int level, String msg){
        if(level==1){
            System.out.println(msg);
        }
    }
}

这段代码存在问题:无论级别是否满足要求,作为log 方法的第二个参数,三个字符串一定会首先被拼接传入方法内然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。

Lambda写法进行改进:只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。

// 定义函数接口
@FunctionalInterface
public interface MessageBuilder {
    public abstract String builderMessage();
}
// Lambda表达式使用
public class LoggerLambda {
    public static void main(String[] args) {
        String msgA = "迪丽热巴";
        String msgB = "中国女性新力量";
        String msgC = "优秀的人";
        log(1,()->msgA+msgB+msgC);
        // 验证:在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。
        log(2,()->{
            System.out.println("Lambda执行!");
            return msgA+msgB+msgC;
        }); // 实际上不执行打印。
    }
    public static void log(int level, MessageBuilder builder){
        if(level==1){
            System.out.println(builder.builderMessage());
        }
    }
}

常用函数式接口

1.Supplier接口

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

Supplier接口称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据。

import java.util.function.Supplier;
public class SupllierTest {
    public static void main(String[] args) {
        // 调用getString方法,方法参数Supplier是一个函数式接口,所以可以考虑使用Lambda表达式
        String s1 = getString(()->{return "迪丽热巴";});
        // 优化Lambda表达式
        String s2 = getString(()->"古力娜扎");
        System.out.println(s1+s2); // 迪丽热巴古力娜扎
    }
    // 方法的参数传递Supplier接口,泛型执行String,那么get方法就会返回一个String
    public static String getString(Supplier sup){
        return sup.get();
    }
}

 练习:求数组最大值,使用该接口实现

// 使用Supplier接口求数组元素中的最大值
import java.util.function.Supplier;
public class SupllierTest {
    public static void main(String[] args) {
        int[] array = {7,8,-1,10,44,23,2};
        // 调用getMax方法,方法参数Supplier是一个函数式接口,所以可以考虑使用Lambda表达式
        int maxValue = getMax(()->{
            int max = array[0];
            for (int i : array) {
                if(i>max){
                    max = i;
                }
            }
            return max;
        });
        System.out.println(maxValue);
    }
    // 方法的参数传递Supplier接口,泛型执行Integer,那么get方法就会返回一个int
    public static int getMax(Supplier sup){
        return sup.get();
    }
}

2.Consumer接口

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

(1)抽象方法:accept

(仅有一个)抽象方法:void accept(T t) ,意为消费一个指定泛型的数据。

Consumer接口是一个消费性接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据,至于具体怎么消费(或者说使用),需要自定义(比如定义输出、计算....)

import java.util.function.Consumer;
public class ConsumerTest {
    public static void main(String[] args) {
        // 调用method方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
        method("迪丽热巴",(String name)->{
            // 对传递的字符串进行消费
            // 消费方式:直接输出
            System.out.println(name);
            // 消费方式:把字符串进行反转输出
            String reName = new StringBuffer(name).reverse().toString();
            System.out.println(reName);
        });
    }
    // 定义一个方法,方法的参数传递一个字符串的姓名,另一个参数传递Consumer接口,泛型使用String,可以使用Consumer接口消费字符串的姓名。
    public static void method(String name, Consumer con){
        con.accept(name);
    }
}

 (2)默认方法:andthen

default Consumer  andThen(Consumer  after),把两个Consumer接口组合到一起,再对数据进行消费。

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

作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费。 当要连接多个consumer接口时,andThen可以使用链式写法:例如con1.andThen(con2.andThen(con3)).accept(消费的内容)   执行顺序依旧遵照从外到里。

例如:
Consumer con1
Consumer con2
String s = "迪丽热巴";
con1.accept(s);
con2.accept(s);
// 上面两条语句等同下面的一条语句。连接两个Consumer接口,再进行消费。
con1.andThen(con2).accept(s);   // 注意:谁写外面,谁先消费。在这里是con1先消费,然后con2再消费。

下面是JDK的源代码:

default Consumer andThen(Consumer after) {
    Objects.requireNonNull(after);
    return (T t) ‐> { accept(t); after.accept(t); };
}
/*备注: java.util.Objects 的requireNonNull 静态方法将会在参数为null时主动抛出
NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
*/

下面是当方法中传入两个Consumer接口,用andThen连接的示例:

import java.util.function.Consumer;
public class ConsumerTest {
    public static void main(String[] args) {
        // 调用method方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
        method("AngelaBabe",(t)->{
            // 对传递的字符串进行消费, 消费方式:把字符串大写输出
            System.out.println(t.toUpperCase());
        },(s)->{
            // 对传递的字符串进行消费, 消费方式:把字符串小写输出
            System.out.println(s.toLowerCase());
        });
    }
    // 定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用String类型。
    public static void method(String s, Consumer con1, Consumer con2){
        // 这里没有使用andThen默认方法,那么执行顺序就是con1前,con2在后
        con1.accept(s);
        con2.accept(s);
        System.out.println("---------------------------");
        // 使用andThen方法,可以简写上面两句,将两个Consumer接口连接到一起,并且执行顺序是外面在先里面在后。所以这里的执行顺序是con2在前,con1在后
        con2.andThen(con1).accept(s);
    }
}
/*
输出:
ANGELABABE 
angelababe
---------------------------
angelababe
ANGELABABE
 */

练习:格式化打印信息

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

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

解答:

import java.util.function.Consumer;
public class ConsumerTest {
    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女","古力娜扎,女","赵丽颖,女","张杰,男"};
        method(array,
                (s)-> System.out.println("性别:"+s.split(",")[1]+"。"),
                (m)-> System.out.print("姓名:"+m.split(",")[0]+"。"));  // 注意这里使用的是不换行的print
    }
    // 定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用String类型。
    public static void method(String[] s, Consumer con1, Consumer con2){
        // 使用andThen方法,可以简写上面两句,将两个Consumer接口连接到一起,并且执行顺序是外面在先里面在后。所以这里的执行顺序是con2在前,con1在后
        for (String item : s) {
            con2.andThen(con1).accept(item);
        }
    }
}
/*
输出:
姓名:迪丽热巴。性别:女。
姓名:古力娜扎。性别:女。
姓名:赵丽颖。性别:女。
姓名:张杰。性别:男。
 */

3.Predicate接口

java.util.function.Predicate 接口对某种类型的数据进行判断,从而得到一个boolean值结果。

(1) 抽象方法: test

Predicate 接口中仅包含一个抽象方法: boolean test(T t) 。用来对指定类型数据进行判断的方法。如果符合条件就返回true,如果不符合就返回false。

import java.util.function.Predicate;
public class PredicateTest {
    public static void main(String[] args) {
        String s1 = "abcde";
        String s2 = "abcdef";
        // 调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式。
        boolean b1 = checkString(s1,(str)->{
            // 对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回。
            return str.length()>5;
        });
        // 优化Lambda表达式
        boolean b2 = checkString(s2,str->
            // 对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回。
            str.length()>5);
        // 调用negCheckString方法,和上面条件一样时,进行取反
        boolean b1_1 = negCheckString(s1,str->
            str.length()>5);
        boolean b2_2 = negCheckString(s2,(str)->{
            return str.length()>5;
        });

        System.out.println("b1判断结果:"+b1+"。b2判断结果:"+b2); // b1判断结果:false。b2判断结果:true
        System.out.println("b1_1判断结果:"+b1_1+"。b2_2判断结果:"+b2_2); // b1_1判断结果:true。b2_2判断结果:false
    }
    // 定义一个方法,参数传递一个String类型的字符串;同时传递一个Predicate接口,泛型使用String。使用Predicate中的方法test对字符串进行判断,并把判断的结果返回
    public static boolean checkString(String s, Predicate pre){
        return pre.test(s);
    }
    // 定义一个取反negate方法。
    public static boolean negCheckString(String s, Predicate pre){
        // 如果判断条件不变,则等效于 !pre.test(s);
        return pre.negate().test(s);  // 对条件成真时取反
    }
}

(2)默认方法:and

回忆一下逻辑表达式:可以连接多个判断条件
&&:与运算符,有false则false。
||:或运算,有true则true。
!:非(取反)运算符,非真则假,非假则真。

Predicate接口中有一个默认方法and,表示并且的逻辑关系,也可以用于两个判断条件。其JDK源码为:

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

上述JDK源码方法内部的两个判断条件也是使用&&运算符连接起来的。

例如:
Predicate pre1  // 用来判断是否字符串的长度大于5
Predicate pre2  // 用来判断字符串中是否包含a
String s = "abcdef";
pre1.test(s) && pre2.test(s);  // 连接了两个判断方法,必须同时满足。 
// 上面两条语句等同下面的一条语句。连接两个Predicate接口,再进行判断。
pre1.and(pre2).test(s);   // 注意:谁写外面,谁先判断。在这里是pre1先判断,然后pre2再判断。

import java.util.function.Predicate;
public class PredicateTest {
    public static void main(String[] args) {
        String s1 = "abcde";
        String s2 = "abcdef";
        // 调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式。
        boolean b1 = checkString(s2,(String str)->{
            // 对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回。
            return str.length()>5;
        },   // 对参数传递的字符串进行判断,判断字符串的是否包含字母a,并把判断的结果返回。
                str->str.contains("a"));

        System.out.println("b1判断结果:"+b1/*+"。b2判断结果:"+b2*/); // b1判断结果:true
    }
    // 定义一个方法,参数传递一个String类型的字符串;同时传递一个Predicate接口,泛型使用String。使用Predicate中的方法test对字符串进行判断,并把判断的结果返回
    public static boolean checkString(String s, Predicate pre1,Predicate pre2){
        boolean a = pre1.test(s) && pre2.test(s);
        boolean b = pre1.and(pre2).test(s);
        System.out.println("使用&&运算符的结果:"+a+"  使用and方法的结果:"+b);
        return a&&b;
    }
}

(3)默认方法:or

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

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

 (4)默认方法:negate

“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法negate 的JDK源代码为:

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

 练习:集合信息筛选

题目:数组当中有多条“姓名+性别”的信息如下,请通过Predicate 接口的拼装将符合要求的字符串筛选到集合ArrayList 中,需要同时满足两个条件:1. 必须为女生;2. 姓名为4个字。

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

解答:1.有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断。2.必须同时满足两个条件,所以可以使用and方法连接两个判断条件。

import java.util.function.Predicate;
import java.util.ArrayList;
public class PredicateTest {
    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女","古力娜扎,女","赵丽颖,女","马儿扎哈,男"};
        ArrayList list = checkString(array,
                s->s.split(",")[0].length()==4,
                s->s.split(",")[1].equals("女")
        );
        System.out.println(list.toString());  // [迪丽热巴,女, 古力娜扎,女]
    }

    // 定义一个方法,参数传递一个String类型的数组和两个Predicate接口,泛型使用String,对数组中的信息进行过滤。把满足条件的信息存到ArrayList集合中返回。
    public static ArrayList checkString(String[] strs, Predicate pre1, Predicate pre2) {
        ArrayList array = new ArrayList<>();
        for (String str : strs) {
            if (pre1.and(pre2).test(str)) {
                array.add(str);
            }
        }
        return array;
    }
}

4.Function接口

java.util.function.Function 接口用来根据一个类型的数据得到另一个类型的数据,这个接口中具有两个泛型(T和R)。前者称为前置条件,后者称为后置条件。

(1)抽象方法:apply

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

作用:将T类型转换成R类型。

import java.util.function.Function;
public class FunctionTest {
    public static void main(String[] args) {
        // 定义一个字符串类型的额整数
        String s = "12345";
        // 调用change方法,传递字符串类型的整数和Lambda表达式
        change(s,str->Integer.parseInt(str));  // 输出得到的是12365
    }
    // 定义一个方法。方法的参数传递一个字符串类型的整数;方法的参数传递一个Function接口,泛型使用;使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数。
    private static void change(String s, Function fun) {
        Integer num = fun.apply(s);
        System.out.println(num+20);  // 如果是字符串则数字20不会进行运算,而是直接添加在字符换后面。如果是数字,那么会运算。
    }
}

(2)默认方法:andThen

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

default  Function andThen(Function after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}

需求:把String类型的“123”,转换为Integer类型,加10,结果在转换为String类型

/*
使用默认方法andThen来将多种转换进行连接,组合
*/
import java.util.function.Function;
public class FunctionTest {
    public static void main(String[] args) {
        String s = "123";
        String result = change(s,
                // 将字符串转化为整数+10
                string -> Integer.parseInt(string)+10,
                // 将整数转化成字符串
                integer -> integer + "");
        System.out.println(result); // 133

    }
    // 定义一个方法。方法的参数传递一个字符串类型的整数;方法的参数传递一个Function接口,泛型使用;使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数。
    private static String change(String s, Function fun1,Function fun2) {
        Integer num = fun1.apply(s);
        String str1 = fun2.apply(num);
        // 等效于下面的一句
        String str2 = fun1.andThen(fun2).apply(s); // 从外到里的顺序。
        System.out.println(str1.equals(str2));  // true
        return str2;
    }
}

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

题目:请使用Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:String str = "赵丽颖,20"。(1) 将字符串截取数字年龄部分,得到字符串;(2) 将上一步的字符串转换成为int类型的数字;(3) 将上一步的int数字加100,得到结果int数字。

解答:上面的要求一一对应(1)Function     "赵丽颖,20"  ->  "20"。(2)Function  "20"  ->  20。(3)Function   20  ->  120

import java.util.function.Function;
public class FunctionTest {
    public static void main(String[] args) {
        String s = "赵丽颖,20";
        int result = change(s,
                // 将字符串转化为整数+10
                str1 -> str1.split(",")[1],
                // 将整数转化成字符串
                str2-> Integer.parseInt(str2),
                num ->num+100);
        System.out.println(result); // 120
    }
    private static int change(String s, Function fun1,Function fun2, Function fun3) {
        int result = fun1.andThen(fun2.andThen(fun3)).apply(s);
        return result;
    }
}

你可能感兴趣的:(Java)