• 重学Java8新特性(一) : Lambda表达式、常见函数式接口、方法引用



    前置复习: 匿名内部类思想

    匿名类的用处

    • 代码传递
    • 回调
    • 过滤器

    传递代码块

    当如果有需求, 我们需要将外部的一段代码, 在内部执行, 就考虑可以使用匿名类。

    package com.other.noname;
    
    /**
     * Description:
     *
     * @date 2022/4/2 20:57
     */
    public class Main {
    
        public static void main(String[] args) {
            // 现在要计算一段代码的执行时间, 首先需要想到要创建一个工具类TimeUtil
            // 首先要想到这一点; 伪代码
    //        TimeUtil.test(
    //            // 需要计算时间的代码片段
    //            int count = 0;
    //            for (int i = 0; i < 100; i++) {
    //                count += i;
    //            }
    //        )
            TimeUtil.test(new TimeUtil.Block() {
                @Override
                public void execute() {
                    // 在这里写我们需要执行的代码块
                    for (int i = 0; i < 1000000; i++) {
                        System.out.println(i);
                    }
                }
            });
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    public class TimeUtil {
    
        public interface Block {
            void execute();
        }
    
        // 由于我们的test方法,需要接收一个代码片段, 在我们的工具类中进行时间计算;
        // 所以我们定义一个可以接收代码片段的参数
        public static void test(Block block) {
            long start = System.currentTimeMillis();
            block.execute(); // 最终实际调用了Block的匿名实现类中的execute方法
            long end = System.currentTimeMillis();
            System.out.println("耗时: " + (end - start) / 1000.0 + "秒");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    回调

    package com.other.noname;
    
    /**
     * Description: 测试匿名类用途2, 回调
     *
     *  模拟网络请求
     *
     * @date 2022/4/2 21:31
     */
    public class NetworkUtil {
    
        public interface Block {
            void success(Object response);
            void failure();
        }
    
        public static void get(String url, Block block) {
            // 1. 根据url发送一个异步请求(开启一条子线程)
            // ......
            // 2. 请求完毕后
            boolean result = !url.contains("666");
            if (result) {
                // false, 不包含666, 表示请求失败
                block.failure();
            } else {
                Object response = "我是响应体";
                block.success(response);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    @Test
    public void test02() {
        /*
            这里就展示出了匿名类的回调, 和代码传递很相似
    
            当调用NetworkUtil的get方法时, 我们先将Block的实现类以及它的实现方法实现好, 并作为实参传递给get方法,执行请求
            请求过去之后, 由服务器的响应来判断是否请求成功; 此时再回调我们实现类中的实现方法 success 或 failure
    
         */
        NetworkUtil.get("http://www.it666.com", new NetworkUtil.Block() {
            @Override
            public void success(Object response) {
                System.out.println("请求成功!" + response);
            }
    
            @Override
            public void failure() {
                System.out.println("请求失败!");
            }
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    匿名类在过滤中的使用

    @Test
    public void test03() {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Collection<Integer> filterCollection = FilterUtil.getFilterCollection(list, new FilterUtil.Filter<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer > 2;
            }
        });
    
        filterCollection.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    public class FilterUtil {
    
        public interface Filter<T> {
            boolean test(T t);
        }
    
        public static <T> Collection<T> getFilterCollection(Collection<T> collection, Filter<T> filter) {
            List<T> list = new ArrayList<>();
            collection.forEach(item -> {
                if (filter.test(item)) {
                    list.add(item);
                }
            });
            return list;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    一、Lambda表达式

    • Lambda 是一个匿名函数,我们可以把Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)
    • 使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

    1.1、举例Demo

    public class LambdaDemo {
        public static void main(String[] args) {
            //开启一个线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("新线程执行代码");
                }
            }).start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    分析上面代码

    • 当我们以 new Thread().start() 的方式启动一个线程时,我们做了如下3件事情:
    1. 定义了一个没有名字的类(匿名类) – new Thread(Runnable接口)
    2. 这个类的参数是Runnable 接口
    3. 我们通过 new Runnable(){…}的方式创建了这个类,并重写了该接口的 run() 方法
    • 在这个示例中,其实我们关注的并不是 new Runnable() 这个过程,而是如下2点:
    1. run() 方法 (参数情况)
    2. 以及run方法 {…}方法体中执行的代码

    1.2、Lambda表达式的出现

    • 在以上Demo中,针对使用匿名内部类语法冗余的问题,JDK8推出了 Lambda 表达式。
    • Lambda表达式体现的是函数式编程思想,只需要将要执行的代码放到函数中即可(函数就是类中的方法)
    • Lambda表达式就是一个匿名函数,我们只需要将执行的代码放到 Lambda 表达式中即可。

    1.3、Lambda格式及说明

    Lambda省去面向对象的条条框框,Lambda的标准格式由3部分组成:

    (参数类型 参数名称) -> {
        方法体;
        return 返回值;
    }
    
    • 1
    • 2
    • 3
    • 4

    格式说明

    • (参数类型 参数名称):(接口方法)重写方法的参数列表部分
    • {…}:重写方法的方法体,即要执行的代码部分
    • –>:箭头,无实际含义,起到连接参数列表和方法体的作用

    Lambda 表达式的省略规则

    • 小括号中的参数类型可以省略。
    • 如果小括号中只有一个参数,那么可以省略小括号。
    • 如果大括号中只有一条语句,那么可以同时省略大括号、return关键字及语句分号。

    使用Lambda优化Demo

    public class LambdaDemo {
        public static void main(String[] args) {
            //匿名内部类方式
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("新线程执行代码了");
                }
            }).start();
     
            //体验Lambda表达式: () -> {xxxx}, ()无参数是和Runable中run方法的参数对应的
            // {xxx}是重写run方法的方法体
            new Thread(() ->{
                System.out.println("Lambda表达式执行了");
            }).start();
        }
    }
    
    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    1.4、Lambda练习1

    import org.junit.Test;
    
    import java.util.Comparator;
    
    /**
     * Lambda表达式的使用举例
     */
    public class LambdaTest {
        @Test
        public void test(){
            Runnable r1 = new Runnable() {
                @Override
                public void run() {
                    System.out.println("长安欢迎您");
                }
            };
            r1.run();
    
            System.out.println("+++++++++++++++++++++++++|");
    
            Runnable r2 = () -> System.out.println("长安欢迎您");
    
            r2.run();
        }
    
        @Test
        public void test2(){
            Comparator<Integer> c1 = new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return Integer.compare(o1,o2);
                }
            };
            int compare1 = c1.compare(8,16);
            System.out.println(compare1);
    
            System.out.println("+++++++++++++++++++++++");
    
            //Lambda表达式的写法
            //                        int compare(T o1, T o2);
            Comparator<Integer> c2 = (o1,o2) -> Integer.compare(o1,o2);
    
            int compare2 = c2.compare(28,35);
            System.out.println(compare2);
    
            System.out.println("+++++++++++++++++++++++++++");
    
            //方法引用(暂时可以不关注,后面会介绍)
            Comparator<Integer> c3 = Integer :: compare;
    
            int compare3 = c3.compare(28,35);
            System.out.println(compare3);
        }
    }
    
    @FunctionalInterface
    public interface Comparator<T> {
    	int compare(T o1, T o2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    1.5、Lambda练习2

    import org.junit.Test;
    
    import java.util.ArrayList;
    import java.util.function.Consumer;
    
    /**
     * Lambda表达式的使用
     *
     * 1.举例: (o1,o2) -> Integer.compare(o1,o2);
     * 2.格式:
     *      -> :lambda操作符 或 箭头操作符
     *      ->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
     *      ->右边:lambda体 (其实就是重写的抽象方法的方法体)
     *
     * 3.Lambda表达式的使用:(分为6种情况介绍)
     */
    public class LambdaTest1 {
    
        //语法格式一:无参,无返回值
        @Test
        public void test(){
            Runnable r1 = new Runnable() {
                @Override
                public void run() {
                    System.out.println("长安欢迎您");
                }
            };
            r1.run();
    
            System.out.println("+++++++++++++++++++++++++|");
    
            Runnable r2 = () -> System.out.println("长安欢迎您");
    
            r2.run();
        }
    
        //语法格式二:Lambda 需要一个参数,但是没有返回值。
        @Test
        public void test2(){
            Consumer<String> con = new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            };
            con.accept("善与恶的区别是什么?");
    
            System.out.println("+++++++++++++++++++");
    
            Consumer<String> c1 = (String s) -> {
                System.out.println(s);
            };
            c1.accept("先天人性无善恶,后天人性有善恶。");
        }
        
    @FunctionalInterface
    public interface Consumer<T> {
        void accept(T t);
    }
    
        //语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
        @Test
        public void test3(){
            Consumer<String> c1 = (String s) -> {
                System.out.println(s);
            };
            c1.accept("先天人性无善恶,后天人性有善恶。");
    
            System.out.println("---------------------");
    
            Consumer<String> c2 = (s) -> {
                System.out.println(s);
            };
            c2.accept("如果没有邪恶的话我们怎么会知道人世间的那些善良呢?");
        }
    
        @Test
        public void test4(){
            ArrayList<String> list = new ArrayList<>();//类型推断
    
            int[] arr = {1,2,3};//类型推断
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    1.6、Lambda练习3

    import org.junit.Test;
    
    import java.util.Comparator;
    import java.util.function.Consumer;
    
    /**
     * Lambda表达式的使用
     *
     * 1.举例: (o1,o2) -> Integer.compare(o1,o2);
     * 2.格式:
     *      -> :lambda操作符 或 箭头操作符
     *      ->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
     *      ->右边:lambda体 (其实就是重写的抽象方法的方法体)
     *
     * 3.Lambda表达式的使用:(分为6种情况介绍)
     *
     *    总结:
     *    ->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
     *    ->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字
     */
    public class LambdaTest1 {
    
        //语法格式四:Lambda若只需要一个参数时,参数的小括号可以省略
        @Test
        public void test5(){
            Consumer<String> c1 = (s) -> {
                System.out.println(s);
            };
            c1.accept("先天人性无善恶,后天人性有善恶。");
    
            System.out.println("---------------------");
    
            Consumer<String> c2 = s -> {
                System.out.println(s);
            };
            c2.accept("如果没有邪恶的话我们怎么会知道人世间的那些善良呢?");
        }
    
        //语法格式五:Lambda需要两个或以上的参数,多条执行语句,并且可以有返回值
        @Test
        public void test6(){
            Comparator<Integer> c1 = new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    System.out.println(o1);
                    System.out.println(o2);
                    return o1.compareTo(o2);
                }
            };
            System.out.println(c1.compare(15,23));
    
            System.out.println("\\\\\\\\\\\\\\\\\\\\\\\\\\");
    
            Comparator<Integer> com2 = (o1,o2) -> {
                System.out.println(o1);
                System.out.println(o2);
                return o1.compareTo(o2);
            };
            System.out.println(com2.compare(16,8));
        }
    
        //语法格式六:当Lambda体只有一条语句时,return与大括号若有,都可以省略
        @Test
        public void test7(){
            Comparator<Integer> c1 = (o1,o2) -> {
                return o1.compareTo(o2);
            };
    
            System.out.println(c1.compare(16,8));
    
            System.out.println("\\\\\\\\\\\\\\\\\\\\\\\\\\");
    
            Comparator<Integer> c2 = (o1,o2) -> o1.compareTo(o2);
    
            System.out.println(c2.compare(17,24));
        }
    
        @Test
        public void test8(){
            Consumer<String> c1 = s -> {
                System.out.println(s);
            };
            c1.accept("先天人性无善恶,后天人性有善恶。");
    
            System.out.println("---------------------");
    
            Consumer<String> c2 = s -> System.out.println(s);
    
            c2.accept("如果没有邪恶的话我们怎么会知道人世间的那些善良呢?");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    1.7、Lambda表达式使用的前提条件

    • 方法的参数或局部变量类型必须为接口,才能使用Lambda
    • 接口中有且仅有一个抽象方法, DK8中,只有一个抽象方法的接口称为函数式接口,我们就能使用 Lambda。
      • 针对一个接口中,是否有大于一个抽象方法?JDK8为我们新增了一个注解:@FunctionalInterface。它能够帮助我们检测这个接口是不是只有一个抽象方法,如果有两个抽象方法,则会报错。

    1.8、Lambda 和 匿名内部类对比

    • 1.所需的类型不一样
      • 匿名内部类,需要的类型可以使类,抽象类,接口;
      • Lambda表达式,需要的类型必须是接口。
    • 2.抽象方法的数量不一样
      • 匿名内部类所需的接口中抽象方法的数量随意;
      • Lambda表达式所需的接口只能有一个抽象方法。
    • 3.实现原理不同
      • 匿名内部类是在编译后,会形成额外的一个 类名$0 的.class文件
      • Lambda 表达式实在程序运行的时候动态生成 .class 文件

    二、函数式(Functional)接口

    2.1、函数式接口的介绍

    /*
     * 4.Lambda表达式的本质:作为函数式接口的实例
     *
     * 5. 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,
     *   这样做可以检查它是否是一个函数式接口。
     *
     */
    
    /**
     * 自定义函数式接口
     */
    public interface MyInterFace {
    
        void method();
    
    //    void method2();
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • java.util.function包下定义了Java 8 的丰富的函数式接口
    • 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示
    • 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。

    2.2、Java内置的函数式接口介绍及使用举例

    函数式接口参数类型返回类型用途
    Consumer 消费型接口Tvoid对类型为T的对象应用操作,包含方法:void accept(T t)
    Supplier 供给型接口T返回类型为T的对象,包含方法:T get()
    Function 函数型接口TR对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
    Predicate断定型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法:boolean test(T t)
    BiFunctionT, UR对类型为T,U参数应用操作,返回R类型的结果。包含方法为:R apply(T t,U u);
    UnaryOperator(Function子接口)TT对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为:T apply(T t);
    BinaryOperator(BiFunction子接口)T,TT对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为:T apply(T t1,T t2);
    BiConsumerT,Uvoid对类型为T,U参数应用操作。包含方法为:void accept(Tt,Uu)
    BiPredicateT,Uboolean包含方法为:boolean test(Tt,Uu)
    ToIntFunctionTint计算int值的函数
    ToLongFunctionTlong计算long值的函数
    ToDoubleFunctionTdouble计算double值的函数
    IntFunctionintR参数为int类型的函数
    LongFunctionlongR参数为long类型的函数
    DoubleFunctiondoubleR参数为double类型的函数
    import org.junit.Test;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.function.Consumer;
    import java.util.function.Predicate;
    
    /**
     * java内置的4大核心函数式接口
     *
     * 消费型接口 Consumer     void accept(T t)
     * 供给型接口 Supplier     T get()
     * 函数型接口 Function   R apply(T t)
     * 断定型接口 Predicate    boolean test(T t)
     */
    public class LambdaTest2 {
    
    	// ----消费型接口 Consumer     void accept(T t)
        public void happyTime(double money, Consumer<Double> con) {
            con.accept(money);
        }
    
        @Test
        public void test(){
            happyTime(30, new Consumer<Double>() {
                @Override
                public void accept(Double aDouble) {
                    System.out.println("熬夜太累了,点个外卖,价格为:" + aDouble);
                }
            });
            System.out.println("+++++++++++++++++++++++++");
    
            //Lambda表达式写法
            happyTime(20, money -> System.out.println("熬夜太累了,吃口麻辣烫,价格为:" + money));
        }
    
    	// -----供给型接口 Supplier     T get()
        //根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
        public List<String> filterString(List<String> list, Predicate<String> pre){
    
            ArrayList<String> filterList = new ArrayList<>();
            for(String s : list){
                if(pre.test(s)){
                    filterList.add(s);
                }
            }
            return filterList;
        }
    
        @Test
        public void test2(){
            List<String> list = Arrays.asList("长安","上京","江南","渝州","凉州","兖州");
    
            List<String> filterStrs = filterString(list, new Predicate<String>() {
                @Override
                public boolean test(String s) {
                    return s.contains("州");
                }
            });
    
            System.out.println(filterStrs);
    
            List<String> filterStrs1 = filterString(list,s -> s.contains("州"));
            System.out.println(filterStrs1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    Supplier 供给型接口 (优化不一定执行的代码)

    T get();

    /**
     * Description: 供给型接口
     *
     *  T get();
     *
     *  有时使用 Supplier 传参,可以避免代码的浪费执行(有必要的时候再执行)
     */
    public class SupplierTest {
    
        /*
            现在有个需求, 如果第一个字符串不为空, 则返回第一个字符串, 否则返回第二个字符串
         */
        public static void main(String[] args) {
            System.out.println(getFirstNotEmptyString("Jack", makeString()));
        }
    
        public static String getFirstNotEmptyString(String str1, String str2) {
            if (str1 != null && str1.length() > 0) return str1;
            if (str2 != null && str2.length() > 0) return str2;
            return null;
        }
    
        private static String makeString() {
            System.out.println("makeString");
            return String.format("%d %d %d", 1, 2, 3);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    此时发现, 如果第一个参数不为空之后, 还调用了makeString()方法, 明显存在浪费调用; 所以可以使用Supplier来优化

    /**
     * Description: 供给型接口
     * 

    * T get(); *

    * 有时使用 Supplier 传参,可以避免代码的浪费执行(有必要的时候再执行) * * @date 2022/4/3 18:58 */ public class SupplierTest { /* 现在有个需求, 如果第一个字符串不为空, 则返回第一个字符串, 否则返回第二个字符串 */ public static void main(String[] args) { //System.out.println(getFirstNotEmptyString("Jack", makeString())); System.out.println(getFirseNotEmptyString("Jack", new Supplier<String>() { @Override public String get() { return makeString(); } })); //System.out.println(getFirseNotEmptyString("Jack", () -> makeString())); //System.out.println(getFirseNotEmptyString("Jack", SupplierTest::makeString)); } // public static String getFirstNotEmptyString(String str1, String str2) { // if (str1 != null && str1.length() > 0) return str1; // if (str2 != null && str2.length() > 0) return str2; // return null; // } // 优化 public static String getFirseNotEmptyString(String str1, Supplier<String> supplier) { if (str1 != null && str1.length() > 0) return str1; String str2 = supplier.get(); if (str2 != null && str2.length() > 0) return str2; return null; } private static String makeString() { System.out.println("makeString"); return String.format("%d %d %d", 1, 2, 3); } } // 此时就完成了优化, 延迟了makeString()方法的调用

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    Consumer供给型接口 (接收一个元素决定做什么)

    void accept(T t)

        public static void main(String[] args) {
            /*
                Consumer这个函数式接口, 用来接收我们提供的元素, 具体操作这些元素由调用者来决定
            */
            Integer[] nums = {1, 2, 3, 4};
            // 比如, 我们要遍历所有元素
            for (Integer num : nums) {
                System.out.println(num);
            }
            // 比如, 我们要遍历所有都+100的元素
            for (Integer num : nums) {
                System.out.println(num + 100);
            }
            // 上面的代码, 实现了需求, 但是具体对元素的操作是千变万化的, eg: 遍历出偶数/基数元素等,我们还需要再写不同的逻辑进行处理
            // 此时可以使用Consumer函数式接口
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    public class ConsumerTest {
    
        public static void main(String[] args) {
            /*
                Consumer这个函数式接口, 用来接收我们提供的元素, 具体操作这些元素由调用者来决定
            */
            Integer[] nums = {1, 2, 3, 4};
            // 比如, 我们要遍历所有元素
            for (Integer num : nums) {
                System.out.println(num);
            }
            // 比如, 我们要遍历所有都+100的元素
            for (Integer num : nums) {
                System.out.println(num + 100);
            }
             System.out.println("--------------------------");
            // 上面的代码, 实现了需求, 但是具体对元素的操作是千变万化的, eg: 遍历出偶数/基数元素等,我们还需要再写不同的逻辑进行处理
            // 此时可以使用Consumer函数式接口
            forEach(nums, System.out::println);
            forEach(nums, integer -> System.out.println(integer + 100));
    
        }
    
    //    public static void forEach(Integer[] nums, Consumer consumer) {
    //        if (nums == null || consumer == null) return;
    //        for (Integer num : nums) {
    //            consumer.accept(num); // 这里是Consumer的接口方法, 具体的实现方法, 由调用者来决定
    //        }
    //    }
    
        // 对上面代码的优化, 变成泛型方法, 增加通用性
        public static <T> void forEach(T[] nums, Consumer<T> consumer) {
            if (nums == null || consumer == null) return;
            for (T num : nums) {
                consumer.accept(num); // 这里是Consumer的接口方法, 具体的实现方法, 由调用者来决定
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    andThen的使用

    public static void main(String[] args) {
    	int[] nums = { 11, 33, 44, 88, 77, 66 };
    	forEach(nums, (n) -> {
    		String result = ((n & 1) == 0) ? "偶数" : "奇数";
    		System.out.println(n + "是" + result);
    	}, (n) -> {
    		String result = ((n % 3) == 0) ? "能" : "不能";
    		System.out.println(n + result + "被3整除");
    	});
    }
    
    static void forEach(int[] nums, Consumer<Integer> c1, Consumer<Integer> c2) {
    	if (nums == null || c1 == null || c2 == null) return;
    	for (int n : nums) {
    		// 相当于先执行c1的任务, 然后执行c2的任务, 执行完后进入下一轮循环
    		c1.andThen(c2).accept(n);
    	}
    }
    
    11是奇数
    11不能被3整除
    33是奇数
    33能被3整除
    44是偶数
    44不能被3整除
    88是偶数
    88不能被3整除
    77是奇数
    77不能被3整除
    66是偶数
    66能被3整除
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    Predicate函数式接口 (让过滤条件更灵活)

    boolean test(T t);

    /**
     * Description: 应用常见, 过滤操作
     * 

    * boolean test(T t); * * @author guizy * @date 2022/4/3 19:31 */ public class PredicateTest { public static void main(String[] args) { /* 对数组中{11, 22, 33, 44, 55, 66}的偶数用_进行拼接 */ Integer[] nums = {11, 22, 33, 44, 55, 66}; join(nums, new Predicate<Integer>() { @Override public boolean test(Integer integer) { return integer % 2 == 0; // 表示integer为偶数的时候, 才进行拼接 } }); System.out.println("-------------------"); join(nums, item -> item % 2 == 0); } public static void join(Integer[] nums, Predicate<Integer> predicate) { if (nums == null || predicate == null) return; for (Integer num : nums) { if (predicate.test(num)) { System.out.println(num + "_"); } } } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    Function(类型转换)

    R applay(T t);

    /**
     * Description: Fucntion 类型转换
     *
     *  R apply(T t);
     *
     */
    public class FunctionTest {
    
        public static void main(String[] args) {
            /*
                需求, 将传入的字符串数字的数组, 求和
             */
    
            System.out.println(getSum(new String[]{"1", "2", "3"}, new Function<String, Integer>() {
                @Override
                public Integer apply(String s) {
                    return Integer.valueOf(s);
                }
            }));
            
            System.out.println(getSum(new String[]{"1", "2", "3"}, Integer::valueOf));
        }
    
        public static int getSum(String[] array, Function<String, Integer> function) {
            if (array == null || function == null) return 0;
            int result = 0;
            for (String str : array) {
                result += function.apply(str);
            }
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    三、方法引用与构造器引用

    • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
    • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
    • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
    • 格式:使用操作符“::” 将类(或对象) 与方法名分隔开来。
    • 如下三种主要使用情况:
      • 对象::实例方法名
      • 类::静态方法名
      • 类::实例方法名

    3.1、方法引用的使用情况1

    1、Employee类

    @Data
    public class Employee {
    
    	private int id;
    	private String name;
    	private int age;
    	private double salary;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2、测试类

    import org.junit.Test;
    
    import java.io.PrintStream;
    import java.util.Comparator;
    import java.util.function.BiPredicate;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Supplier;
    
    /**
     * 方法引用的使用
     *
     * 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
     *
     * 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以
     *   方法引用,也是函数式接口的实例。
     *
     * 3. 使用格式:  类(或对象) :: 方法名
     *
     * 4. 具体分为如下的三种情况:
     *    情况1     对象 :: 非静态方法
     *    情况2     类 :: 静态方法
     *
     *    情况3     类 :: 非静态方法
     *
     * 5. 方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的
     *    形参列表和返回值类型相同!(针对于情况1和情况2)
     */
    public class MethodRefTest {
        
        // 情况一:对象 :: 实例方法
        //Consumer中的void accept(T t)
        //PrintStream中的void println(T t)
        @Test
        public void test() {
            Consumer<String> c1 = str -> System.out.println(str);
            c1.accept("兖州");
    
            System.out.println("+++++++++++++");
            PrintStream ps = System.out;
            Consumer<String> c2 = ps::println;
            c2.accept("xian");
        }
    
        //Supplier中的T get()
        //Employee中的String getName()
        @Test
        public void test2() {
            Employee emp = new Employee(004,"Nice",19,4200);
    
            Supplier<String> sk1 = () -> emp.getName();
            System.out.println(sk1.get());
    
            System.out.println("*******************");
            Supplier<String> sk2 = emp::getName;
            System.out.println(sk2.get());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    3.2、方法引用的使用情况2

    1、Employee类——同上

    2、测试类

    import org.junit.Test;
    import java.util.Comparator;
    import java.util.function.Function;
    
    public class MethodRefTest {
    
        // 情况二:类 :: 静态方法
        //Comparator中的int compare(T t1,T t2)
        //Integer中的int compare(T t1,T t2)
        @Test
        public void test3() {
            Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1,t2);
            System.out.println(com1.compare(21,20));
    
            System.out.println("+++++++++++++++");
    
            Comparator<Integer> com2 = Integer::compare;
            System.out.println(com2.compare(15,7));
        }
    
        //Function中的R apply(T t)
        //Math中的Long round(Double d)
        @Test
        public void test4() {
            Function<Double,Long> func = new Function<Double, Long>() {
                @Override
                public Long apply(Double d) {
                    return Math.round(d);
                }
            };
    
            System.out.println("++++++++++++++++++");
    
            Function<Double,Long> func1 = d -> Math.round(d);
            System.out.println(func1.apply(14.1));
    
            System.out.println("++++++++++++++++++");
    
            Function<Double,Long> func2 = Math::round;
            System.out.println(func2.apply(17.4));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    3.3、方法引用的使用情况3

    1、Employee类——同上

    2、测试类

    import org.junit.Test;
    
    import java.util.Comparator;
    import java.util.function.BiPredicate;
    import java.util.function.Function;
    
    public class MethodRefTest {
    
        // 情况三:类 :: 实例方法  (有难度)
        // Comparator中的int comapre(T t1,T t2)
        // String中的int t1.compareTo(t2)
        @Test
        public void test5() {
            Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
            System.out.println(com1.compare("abc","abd"));
    
            System.out.println("++++++++++++++++");
    
            Comparator<String> com2 = String :: compareTo;
            System.out.println(com2.compare("abd","abm"));
        }
    
        //BiPredicate中的boolean test(T t1, T t2);
        //String中的boolean t1.equals(t2)
        @Test
        public void test6() {
            BiPredicate<String,String> pre1 = (s1, s2) -> s1.equals(s2);
            System.out.println(pre1.test("MON","MON"));
    
            System.out.println("++++++++++++++++++++");
            
            BiPredicate<String,String> pre2 = String :: equals;
            System.out.println(pre2.test("MON","MON"));
        }
    
        // Function中的R apply(T t)
        // Employee中的String getName();
        @Test
        public void test7() {
            Employee employee = new Employee(007, "Ton", 21, 8000);
    
            Function<Employee,String> func1 = e -> e.getName();
            System.out.println(func1.apply(employee));
    
            System.out.println("++++++++++++++++++++++++");
    
            Function<Employee,String> f2 = Employee::getName;
            System.out.println(f2.apply(employee));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    3.4、构造器引用与数组引用的使用

    格式:ClassName::new

    与函数式接口相结合,自动与函数式接口中方法兼容。

    可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。

    1、Employee类——同上

    2、测试类

    import org.junit.Test;
    
    import java.util.Arrays;
    import java.util.function.BiFunction;
    import java.util.function.Function;
    import java.util.function.Supplier;
    
    /**
     * 一、构造器引用
     *      和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
     *      抽象方法的返回值类型即为构造器所属的类的类型
     *
     * 二、数组引用
     *     可以把数组看做是一个特殊的类,则写法与构造器引用一致。 
     */
    public class MethodRefTest {
    
        //构造器引用
        //Supplier中的T get()
        //Employee的空参构造器:Employee()
        @Test
        public void test() {
            Supplier<Employee> sup = new Supplier<Employee>() {
                @Override
                public Employee get() {
                    return new Employee();
                }
            };
            System.out.println("+++++++++++++++++++");
    
            Supplier<Employee> sk1 = () -> new Employee();
            System.out.println(sk1.get());
    
            System.out.println("+++++++++++++++++++");
    
            Supplier<Employee> sk2 = Employee::new;
            System.out.println(sk2.get());
        }
    
        //Function中的R apply(T t)
        @Test
        public void test2() {
            Function<Integer, Employee> f1 = id -> new Employee(id);
            Employee employee = f1.apply(7793);
            System.out.println(employee);
    
            System.out.println("+++++++++++++++++++");
    
            Function<Integer, Employee> f2 = Employee::new;
            Employee employee1 = f2.apply(4545);
            System.out.println(employee1);
        }
    
        //BiFunction中的R apply(T t,U u)
        @Test
        public void test3() {
            BiFunction<Integer, String, Employee> f1 = (id, name) -> new Employee(id, name);
            System.out.println(f1.apply(2513, "Fruk"));
    
            System.out.println("*******************");
    
            BiFunction<Integer, String, Employee> f2 = Employee::new;
            System.out.println(f2.apply(9526, "Bon"));
        }
    
        //数组引用
        //Function中的R apply(T t)
        @Test
        public void test4() {
            Function<Integer, String[]> f1 = length -> new String[length];
            String[] arr1 = f1.apply(7);
            System.out.println(Arrays.toString(arr1));
    
            System.out.println("+++++++++++++++++++");
    
            Function<Integer, String[]> f2 = String[]::new;
            String[] arr2 = f2.apply(9);
            System.out.println(Arrays.toString(arr2));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
  • 相关阅读:
    【JavaEE】Servlet API 详解(HttpServletResponse类方法演示、实现自动刷新、实现自动重定向)
    Go语言学习笔记-A Tour of Go 练习笔记-Readers
    java计算机毕业设计基于安卓Android的学生个人支出管理APP
    Leetcode79单词搜索
    排序的本质:
    Kubernetes:详解如何将CPU Manager做到游刃有余
    从单个/两个向量构建一组正交基底
    深入理解和把握数字经济的基本特征
    centos Let‘s Encrypt 免费https证书申请,并且自动续约
    图搜索算法详解
  • 原文地址:https://blog.csdn.net/m0_37989980/article/details/126082877