理解 Lambda 表达式

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 表达式_第1张图片

那么如何把“一块代码”赋给 Java 变量呢?如图把右边代码,赋给左边的 Java 变量 aBlockOfCode:
理解 Lambda 表达式_第2张图片

Java8之后可以利用 Lambda 特性做到,如图:理解 Lambda 表达式_第3张图片

当然,该写法并不简洁。为使该赋值操作更加简洁,可移除一些无用的声明,如下:理解 Lambda 表达式_第4张图片

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

Java8 中所有的 Lambda 的类型都是一个接口。而 Lambda 表达式本身,也就是“那块代码”,需要是该接口的实现。这是理解 Lambda 的关键所在,简言之,Lambda 表达式本身就是一个接口的实现。便于理解,给 aBlockOfCode 加上一个类型:理解 Lambda 表达式_第5张图片

这种只有一个函数需要被实现的接口类型,为“函数式接口”。为避免后人在该接口中增加接口函数导致其有多个接口方法需要被实现,变成"非函数接口”,可以在上面加上一个声明@FunctionalInterface,如此就无法在里面添加新的接口函数了:理解 Lambda 表达式_第6张图片

如此就得到了一个完整的 Lambda 表达式声明:

三、Lambda 表达式:最直观的作用就是使得代码变得异常简洁。

对比一下 Lambda 表达式和传统的 Java 对同一个接口的实现:理解 Lambda 表达式_第7张图片

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

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

四、Lambda 实践

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

假设 Person 的定义和 List 的值都给定:理解 Lambda 表达式_第9张图片

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

原生态 Lambda 写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值 Lambda 表达式。理解 Lambda 表达式_第10张图片

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

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

第一步简化 - 利用函数式接口包:理解 Lambda 表达式_第12张图片

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

第二步简化 - 用 Iterable.forEach() 取代 foreach loop:理解 Lambda 表达式_第13张图片

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

第三步简化 - 利用 stream() 替代静态函数:理解 Lambda 表达式_第14张图片

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

第四步简化 - 如果是 println§,则可以利用 Method reference 代替 forEach 中的 Lambda 表达式:理解 Lambda 表达式_第15张图片

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

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

假设有一个 person object,以及一个 person object 的 Optional wrapper:理解 Lambda 表达式_第16张图片

Optional 如果不结合 Lambda 使用的话,并不能使原来繁琐的 null check 变的简单。理解 Lambda 表达式_第17张图片

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

情况一 - 存在则开干理解 Lambda 表达式_第18张图片

情况二 - 存在则返回,不存在则自定义返回理解 Lambda 表达式_第19张图片

情况三 - 存在则返回,无则由函数产生理解 Lambda 表达式_第20张图片

情况四 - 夺命连环 null 检查理解 Lambda 表达式_第21张图片

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

你可能感兴趣的:(Java,java)