Java8 新特性 (一) Lambda表达式

一、Lambda 表达式

  1、为什么使用 Lambda 表达式

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

  2、函数式编程思想概述

     在数学中,函数就是有输入量、输出量的一套计算方案,就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法—— 强调做什么,而不是以什么形式做。

     面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法完成事情。

     函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。

  3、冗余的 Runnable 代码

    传统写法

    当需要启动一个线程去完成任务时,通常会通过  java.lang.Runnable  接口来定义任务内容,并使用 java.lang.Thread 类来启动线程。

    Demo:

 1 public class Demo01Runnable {
 2     public static void main(String[] args) {
 3         // 匿名内部类
 4         Runnable task = new Runnable() {
 5             @Override
 6             public void run() { // 覆盖重写抽象方法
 7                 System.out.println("多线程任务执行!");
 8             }
 9         };
10         new Thread(task).start(); // 启动线程
11     }
12 }

    面向对象的思想:首先创建一个 Runnable 接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。

    代码分析

     对于 Runnable 的匿名内部类用法:可以分析出几点内容:

     ① Thread 类需要 Runnable 接口作为参数,其中的抽象方法 run 方法是用来指定线程任务内容的核心

        ② 为了指定 run 的方法体,必须需要 Runnable 接口的实现类;

        ③ 为了省去定义一个 Runnable 实现类的麻烦,必须使用匿名内部类

        ④ 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值必须再写一遍,且不能写错

        ⑤ 实际上,似乎只有方法体才是关键所在

  4、编程思想转换

    对于上面的案例来说,我们真的希望创建一个匿名内部类对象吗?并不需要,只是为了做这件事情不得不创建一个对象。

    真正希望做的是:将 run 方法体内的代码传递给 Thread 类。

    传递一段代码——这才是真正的目的。而创建对象只是受限于面向对象而不得不采取的一种手段方式。

    如果我们将关注点从“怎么做”回归到“做什么” 的本质上,就会发现只要能够更好地达到母的,过程与形式其实并不重要。

  5、Lambda 的更优写法

    JDK8 之后,有了全新的语法,上面的 Runnable 接口的匿名内部类写法可以通过更简单的 Lambda 表达式达到等效:

1 public class Demo02LambdaRunnable {
2     public static void main(String[] args) {
3         new Thread(() ‐> System.out.println("多线程任务执行!")).start(); // 启动线程
4     }
5 }

    这段代码和上面的执行效果是一样的。从代码的语义上可以看出:启动了一个线程,而线程任务的内容以一种更简洁的形式被指定。

    优点:不再有“必须创建接口对象” 的束缚,不再有 “抽象方法覆盖重写” 的负担。

    案例一:

    

    案例二:

    

  6、匿名内部类到 Lambda 的转换

    Lambda 表达式是怎么做到的呢?核心代码如下:

() ‐> System.out.println("多线程任务执行!")

    为了理解 Lambda 的语义,先来看一下以前的实现方式。

      (1)使用实现类

      要启动一个线程,需要创建一个 Thread 类的对象并调用 start 方法,而为了指定线程执行的内容,需要调用 thread 类的构造方法:

public Thread(Runnable target)

     为了获取 Runnable 接口的实现对象,可以为该接口定义一个实现类 RunnableImplement:

1 public class RunnableImpl implements Runnable {
2     @Override
3     public void run() {
4         System.out.println("多线程任务执行!");
5     }
6 }

     然后创建该实现类的对象作为 Thread 类的构造参数:

1 public class Demo03ThreadInitParam {
2     public static void main(String[] args) {
3         Runnable task = new RunnableImpl();
4         new Thread(task).start();
5     }
6 }

    (2)使用匿名内部类

      这个 RunnableImpl 类只是为了实现 Runnable 接口存在的,而且仅被使用了唯一一次,所以使用匿名内部类的语法即可省去该类的单独定义,即匿名内部类:

 1 public class Demo04ThreadNameless {
 2     public static void main(String[] args) {
 3         new Thread(new Runnable() {
 4             @Override
 5             public void run() {
 6                 System.out.println("多线程任务执行!");
 7             }
 8         }).start();
 9     }
10 }

    (3)匿名内部类的好处与弊端

      好处:匿名内部类可以省去实现类的定义;

      缺点:匿名内部类语法复杂

  语义分析:

    经分析上述代码中的语义,Runnable 接口只有一个 run 方法的定义:

public abstract void run();

    即制定了一种做事情的方案(本质为一个函数):

    ① 无参数:不需要任何条件即可执行该方案

    ② 无返回值:该方案不产生任何结果

    ③ 代码块(方法体):该方案的具体执行步骤

    同样的语义体现在 Lambda 语法中,会更加简单:

() ‐> System.out.println("多线程任务执行!")

    ① 前面的一对小括号即 run 方法的参数(无),代表不需要任何条件;

        ② 中间的一个箭头代表前面的参数传递给后面的代码

        ③ 后面的输出语句即业务逻辑代

  7、

 

二、Lambda表达式:语法

  1、Lambda 表达式

    Lambda 表达式: 在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 ->, 该操作符被称为 Lambda 操作符箭头操作符。它将 Lambda 分为两个部分:

    左侧:指定了 Lambda 表达式需要的参数列表(其实就是接口中的抽象方法的形参列表)

    右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能(其实就是重写的抽象方法的方法体)

    Lambda 表达式的标准格式为:

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

    格式说明

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

      Lambda 表达式:是可推导,可以省略,凡是根据上下文推导出来的内容,都可以省略书写。

    可以省略的内容:

     ① (参数列表):括号中参数列表的数据类型,可以省略不写

     ② (参数列表):括号中的参数如果只有一个,那么类型和()都可以省略

     ③ {一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)

      注意:要省略{},return,分号必须一起省略

  2、Lambda 的使用前提

    Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意: 

    (1)使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法 

    (2)无论是JDK内置的 Runnable 、 Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。 

    (3)使用Lambda必须具有上下文推断 。
      也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。 

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

  3、Lambda表达式的本质:作为函数式接口的实例

三、Lambda 表达式的使用

  1、语法格式一:无参,无返回值

    Demo:

 1     @Test
 2     public void test1(){
 3         Runnable r1 = new Runnable() {
 4             @Override
 5             public void run() {
 6                 System.out.println("Hello World");
 7             }
 8         };
 9 
10         r1.run();
11 
12         System.out.println("***********************");
13 
14         Runnable r2 = () -> {
15             System.out.println("Hello Java");
16         };
17 
18         r2.run();
19     }

  2、语法格式二:Lambda 需要一个参数,但是没有返回值

    Demo:

 1     @Test
 2     public void test2(){
 3 
 4         Consumer<String> con = new Consumer<String>() {
 5             @Override
 6             public void accept(String s) {
 7                 System.out.println(s);
 8             }
 9         };
10         con.accept("谎言和誓言的区别是什么?");
11 
12         System.out.println("*******************");
13 
14         Consumer<String> con1 = (String s) -> {
15             System.out.println(s);
16         };
17         con1.accept("一个是听得人当真了,一个是说的人当真了");
18 
19     }

  3、语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

    Demo:

 1     @Test
 2     public void test3(){
 3 
 4         Consumer<String> con1 = (String s) -> {
 5             System.out.println(s);
 6         };
 7         con1.accept("一个是听得人当真了,一个是说的人当真了");
 8 
 9         System.out.println("*******************");
10 
11         Consumer<String> con2 = (s) -> {
12             System.out.println(s);
13         };
14         con2.accept("一个是听得人当真了,一个是说的人当真了");
15 
16     }

  4、语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略

    Demo:

 1     @Test
 2     public void test4(){
 3         Consumer<String> con1 = (s) -> {
 4             System.out.println(s);
 5         };
 6         con1.accept("一个是听得人当真了,一个是说的人当真了");
 7 
 8         System.out.println("*******************");
 9 
10         Consumer<String> con2 = s -> {
11             System.out.println(s);
12         };
13         con2.accept("一个是听得人当真了,一个是说的人当真了");
14 
15     }

  5、语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值

    Demo:

 1     @Test
 2     public void test5(){
 3 
 4         Comparator<Integer> com1 = new Comparator<Integer>() {
 5             @Override
 6             public int compare(Integer o1, Integer o2) {
 7                 System.out.println(o1);
 8                 System.out.println(o2);
 9                 return o1.compareTo(o2);
10             }
11         };
12 
13         System.out.println(com1.compare(12,21));
14 
15         System.out.println("*****************************");
16         Comparator<Integer> com2 = (o1,o2) -> {
17             System.out.println(o1);
18             System.out.println(o2);
19             return o1.compareTo(o2);
20         };
21 
22         System.out.println(com2.compare(12,6));
23 
24     }

  6、语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略

    Demo:

 1     @Test
 2     public void test6(){
 3 
 4         Comparator<Integer> com1 = (o1,o2) -> {
 5             return o1.compareTo(o2);
 6         };
 7 
 8         System.out.println(com1.compare(12,6));
 9 
10         System.out.println("*****************************");
11 
12         Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
13 
14         System.out.println(com2.compare(12,21));
15 
16     }

   7、总结

    ->左边:lambda 形参列表的参数类型可以省略(类型推断);如果 lambda 形参列表只有一个参数,其一对()也可以省略

    ->右边:lambda 体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字

四、Lambda 中的类型推断

    上述 Lambda 表达式中的参数类型都是由编译器推断得出的。 Lambda表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。 Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的类型推断

    

五、

原文地址:https://www.cnblogs.com/niujifei/p/14916049.html