System.out::println是什么 ? Lambda表达式和方法引用

2024-01-02 09:37:48

System.out::printlin 可以很好的串联Java8新特性中的Lambda表达式和方法引用

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        //完成对集合元素的遍历输出
        list.forEach(System.out::println);

  1. 首先用Lambda体简化匿名内部类
  2. 了解函数式接口的概念
  3. 方法引用的用法
  4. Consumer<T> 的场景
  5. 引出函数式编程
  6. System.out::println

链条式引出结果, 并非完整文档笔记.

从匿名内部类 到 Lambda

Lambda本质就是一种书写方式 , 他的整体就是一个对象

匿名内部类写法

假如要创建接口对象, 一种方式就是写成匿名内部类 ,
这里匿名的说法, 描述的是不用写一个类来继承Runnable , 进而new对象再去调用这个类重写的run方法

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("这是runnable");
            }
        };

Lambda写法

. 匿名内部类这种方式过于复杂, 可以写成Lambda的格式进行优化

        Runnable runnable1 = () -> {
            System.out.println("这是runnable2");
        };

在这里插入图片描述
相当于将 new Runnable() 和 除了方法内的代码给省略
( ) 就是run( ) 的参数表, 接口名方法名都省了.

更简化的Lambda

当这个方法体中只有一行代码时, 可以直接省略{}括号

        Runnable runnable2 = () -> System.out.println("这是runnable2");

所以Lambda体在Java中是对象

在其它语言中Lambda体或者称之为箭头函数./回调函数, 但在Java中很明显Lambda体是对象, 就是函数式接口的一个对象
( ) 就是接口方法的参数
-> 箭头就是参数要传给谁
方法体, 就是要接受参数并实现的逻辑

在这里插入图片描述
但runnable的run方法是一个无参方法,

  1. 当无参方法时, 写成 ( ) -> 方法体
  2. 当有且只有一个方法参数时写成 arg -> 方法体
  3. 当有多个方法参数时 写成 (arg1,arg2) -> 方法体

函数式接口

函数式接口的定义

只有一个方法的接口为函数式接口 , 类似与线程先关的Runnable 接口, 只有一个run( ) 方法 他就是一个函数式接口 ,
而且接口还有函数式接口检查注解@FunctionalInterface, 来用于检查这个接口是不是函数式接口
在这里插入图片描述

函数式编程

函数式接口的对象是Lambda体,
函数式编程就是是像传递参数一样传递一个Lambda体

举例Thread类, 打开Java中的Threa类 , 发现有一个构造方法, 需要一个参数为Runnable类型
在这里插入图片描述
具体写法就是

        Runnable runnable3 = () -> System.out.println("这是runnable3");

        //new Thread(Runnable r)
        Thread thread = new Thread(runnable3);
        

        thread.start();//结果 : 这是runnable3

这就是实现了函数式编程, 传递过去的参数本质上是一个对象, 对象是Runnable接口的一个实例

函数式接口的分类

在这里插入图片描述

Java中内置了4大类型的函数式接口来适应不同的场景, 主要4种但不限于4种,而且还有众多的子接口

  1. Comsumer<T> accept( T ,t ) 只消费传进来的参数没有返回值
  2. Supplier<T> T get() 不接受参数有返回
  3. Function<T,R> R apply (T, t) 对传进来的对象t操作, 返回R类型
  4. Predicate<T> boolean test(T t)

Consumer 用法

Consumer<String> 表明了accept 方法的参数 为String类型, 就是要把传进去的String类型参数给消费掉
诸如"匿名内部类写法" “Lambda写法”


        //匿名内部类写法
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };

        consumer.accept("匿名内部类写法");
        
        
        //Lambda写法
        Consumer<String> consumer1 = s -> System.out.println(s);
        consumer1.accept("Lambda写法");

Consumer在函数式编程中的使用

例如 集合中的forEach方法就是 一个函数式编程的实现 , 最终也将引出System.out::println
方法要传入一个 Comsumer<T> 的对象
在这里插入图片描述

        //创建一个集合
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
         
        // 这里是完整写法 , 可以吧consumer3直接传进去, 也可以直接写简化版Lambda;
        Consumer<Integer> consumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        };

        //将Lambda体传入 , 也就是函数式编程 , 完成对集合的便利
        list.forEach( s -> System.out.println(s));
      

其实在forEach方法中, 接受到 函数式接口对象, 进行非空判断之后, 也是调用对象里的accept方法对每一个元素进行输出操作
在这里插入图片描述

距离System.out::println仅差一步


        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        //将Lambda体传入 , 也就是函数式编程 , 完成对集合的便利
        list.forEach( s -> System.out.println(s));
        //两种方式等同, 就是方法引用对Lambda表达式的再度简化
        list.forEach(System.out::println)

方法引用

System.out::println 就是一种方法引用的写法
方法引用的条件是 函数式接口的方法参数类型和返回值类型, 与 方法引用的参数类型和方法引用类型相同
则Lambda体可以简化书写为
对象::方法 类似于System.out::println
类:: 静态方法 类似于 Math::round

  • 解读

以list.forEach(Consumer<T> com) 为例子
函数式接口的accetpt方法 参数类型Integer并且无返回值
方法体中的println()方法参数类型也是Integer也无返回值
这种场景下省略参数写法, 直接用对象::方法 , 即 System.out::println
这里Syetem.out 等用于一个PrintStream对象 , 用对象::方法


        //创建一个集合
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
         

        Consumer<Integer> consumer = new Consumer<Integer>() {
            @Override
            //  accect方法参数类型为Integer, 无返回值
            public void accept(Integer integer) {
             // System.out 是一个PrintStream对象 , 调用println()方法 该方法参数类型也是Integer也无返回值
                System.out.println(integer);
            }
        };



        //所以这时候不仅可以写成lambda体
        list.forEach( s -> System.out.println(s));
        //也可以使用方法引用  对象::方法
        list.forEach(System.out::println)

实现细节

  1. list集合是ArrayList的对象, 其顶层接口Collection继承自Iterable接口, 所以可以使用其forEach方法
    在这里插入图片描述
    这里forEach 约定参数Consumer的泛型下限为Integer, 因为调用这个方法的实例对象就是Integer类型的集合
    this就是对象本身, 进行便利 , 调用accept对便利出的数字进行输出.
    在这里插入图片描述
    在System.out 获得的PrintStream对象的引用 , 可以重载到Int输出的方法 , 可以发现参数类型于accept都是Integer,
    返回值都是void , 这样就达成方法引用先决条件
    在这里插入图片描述

总结反思

Lambda表达式实现了代码简化, 也更加实际的完成了函数式编程 ,
假如使用匿名内部类方式以下代码就会报错

        List<String> list = Arrays.asList("1", "2", "3", "4", "5");

        Consumer<Integer> consumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        };

        //报错
        list.forEach(consumer);
        
        //正常使用
        list.forEach(System.out::println);


        

如果使用匿名内部类, Consumer的类型要进行声明, 需要比操作的对象需要什么类型的Consumer都要指定好
那么假如调用类型不固定, 由Integer变成了String , 那么就会出问题. 使用Lambda表达式其类型为自动推导
调用者类型即为Consumer<T> T的类型, 更加灵活.

文章来源:https://blog.csdn.net/weixin_48011779/article/details/135331754
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。