理解 Lambda 表达式

时间:2021-6-2 作者:qvyue

Java8 Stream相关集合转换

一、初识 Lambda

简化过程:

  1. 常规实现接口。
  2. 静态内部类。
  3. 局部内部类。
  4. 匿名内部类。
  5. lambda 表达式。
  6. lambda 表达式简化。
public class TestMain {

    static class StaStudent implements Person {
        @Override
        public void speak(String language) {
            System.out.println("静态内部类" + language);
        }
    }

    public static void main(String[] args) {
        Person p = new GloStudent();
        p.speak("lan");

        p = new StaStudent();
        p.speak("lan");

        class LocStudent implements Person {
            @Override
            public void speak(String language) {
                System.out.println("局部内部类" + language);
            }
        }
        p = new LocStudent();
        p.speak("lan");

        p = new Person() {
            @Override
            public void speak(String language) {
                System.out.println("匿名内部类" + language);
            }
        };
        p.speak("lan");

        p = (language) -> {
            System.out.println("lambda 表达式---块代码1" + language);
            System.out.println("lambda 表达式---块代码2" + language);
        };
        p.speak("lan");

        //接口是函数式接口才能转换为lambda表达式
        //在只有一行代码的情况下可以将块简化为行
        //单个参数可以去掉参数类型,括号可选
        //多个参数可以同时去掉参数类型,必须括号包裹
        p = (language) -> System.out.println("lambda 表达式---单行代码" + language);
        p.speak("lan");

    }
}

class GloStudent implements Person {
    @Override
    public void speak(String language) {
        System.out.println("外部类" + language);
    }
}

interface Person {
    void speak(String language);
}

二、什么是 Lambda

对于 Java 变量,可以给其赋值。

理解 Lambda 表达式

那么如何把“一块代码”赋给 Java 变量呢?如图把右边代码,赋给左边的 Java 变量 aBlockOfCode :

理解 Lambda 表达式

Java8 之前,这个是做不到的。Java8 之后,利用 Lambda 特性就可以做到,如图:

理解 Lambda 表达式

当然,这个并不是很简洁的写法。所以,为了使这个赋值操作更加简洁,可以移除一些没用的声明,如下:

理解 Lambda 表达式

这样,就成功的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个 Lambda 表达式。但是这里仍然有一个问题,就是变量 aBlockOfCode 的类型应该是什么?

在 Java8 里面,所有的 Lambda 的类型都是一个接口。而 Lambda 表达式本身,也就是“那块代码”,需要是这个接口的实现。这是理解 Lambda 的一个关键所在,简而言之,Lambda 表达式本身就是一个接口的实现。便于理解,继续往下看。给 aBlockOfCode 加上一个类型:

理解 Lambda 表达式

这种只有一个接口函数需要被实现的接口类型,称之为“函数式接口”。为了避免后来的人在这个接口中增加接口函数导致其有多个接口方法需要被实现,变成”非函数接口”,可以在这个上面加上一个声明@FunctionalInterface,这样就无法在里面添加新的接口函数了:

理解 Lambda 表达式

这样,就得到了一个完整的 Lambda 表达式声明:

理解 Lambda 表达式

三、Lambda 表达式有什么作用

最直观的作用就是使得代码变得异常简洁。

对比一下 Lambda 表达式和传统的 Java 对同一个接口的实现:

理解 Lambda 表达式

两种写法本质上是等价的,但是,显然 Java8 的写法更加优雅简洁。并且由于 Lambda 可以直接赋值给一个变量,就可以直接把 Lambda 作为参数传给函数,而传统的 Java 必须有明确的接口实现的定义,初始化才行:

理解 Lambda 表达式

有些情况下,该接口实现只用到一次。Java7 要求必须定义一个“污染环境”的接口实现 MyInterfaceImpl,而相较之下 Java8 的 Lambda 就显得干净很多。

1️⃣Lambda 结合 FunctionalInterface Lib、forEach、stream()、method reference 等新特性可以使代码变的更加简洁。

假设 Person 的定义和 List 的值都给定:

理解 Lambda 表达式

要打印出 guiltyPersons 里面所有 LastName 以“Z”开头的人的 FirstName。

原生态 Lambda 写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值 Lambda 表达式。

理解 Lambda 表达式

这个代码实际上已经比较简洁了,但是还可以更简洁么?

当然可以。在 Java8 中有一个函数式接口的包,里面定义了大量可能用到的函数式接口java.util.function(Java Platform SE 8)。所以,在这里压根都不需要定义 NameChecker 和 Executor 这两个函数式接口,直接用 Java8 函数式接口包里的 Predicate 和 Consumer 就可以——因为这一对的接口定义和 NameChecker/Executor 其实是一样的。

理解 Lambda 表达式

第一步简化 – 利用函数式接口包:

理解 Lambda 表达式

静态函数里面的 for each 其实是非常碍眼的。这里可以利用 Iterable 自带的 forEach() 来替代。forEach() 本身可以接受一个 Consumer 参数。

第二步简化 – 用 Iterable.forEach() 取代 foreach loop:

理解 Lambda 表达式

由于静态函数其实只是对 List 进行了一通操作,这里可以甩掉静态函数,直接使用 stream() 特性来完成。stream() 的几个方法都是接受 Predicate,Consumer 等参数的java.util.stream(Java Platform SE 8)。理解了上面的内容,stream() 就非常好理解了。

第三步简化 – 利用stream()替代静态函数:

理解 Lambda 表达式

对比最开始的 Lambda 写法,这里已经非常简洁了。但是,如果要求变成 print 这个人的全部信息,及 p -> System.out.println(p); 那么还可以利用 Method reference 来继续简化。所谓 Method reference 就是用已经写好的别的 Object/Class 的 method 来代替 Lambda expression。格式如下:

理解 Lambda 表达式

第四步简化 – 如果是 println(p),则可以利用 Method reference 代替 forEach 中的 Lambda 表达式:

理解 Lambda 表达式

这基本上就是能写的最简洁的版本了。

2️⃣Lambda 配合 Optional 可以使 Java 对于 null 的处理变的异常优雅。

假设有一个 person object,以及一个 person object 的 Optional wrapper:

理解 Lambda 表达式

Optional 如果不结合 Lambda 使用的话,并不能使原来繁琐的 null check 变的简单。

理解 Lambda 表达式

只有当 Optional 结合 Lambda 一起使用的时候,才能发挥出其真正的威力。现在就来对比一下下面四种常见的 null 处理中,Java8 的 Lambda+Optional 和传统 Java 两者之间对于 null 的处理差异。

情况一 – 存在则开干

理解 Lambda 表达式

情况二 – 存在则返回,不存在则自定义返回

理解 Lambda 表达式

情况三 – 存在则返回,无则由函数产生

理解 Lambda 表达式

情况四 – 夺命连环 null 检查

理解 Lambda 表达式

由上述四种情况可以清楚地看到,Optional+Lambda 可以少写很多 if/else。尤其是对于情况四,传统 Java 的写法显得冗长难懂,而新的 Optional+Lambda 则清楚简洁。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。