重学Java 8新特性 | 第4讲——Lambda表达式详解

Posted 李阿昀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重学Java 8新特性 | 第4讲——Lambda表达式详解相关的知识,希望对你有一定的参考价值。


在详细讲解Lambda表达式之前,我们先看一下Lambda表达式的概述,即Lambda表达式是什么?

Lambda表达式是什么?

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

Lambda表达式的基础语法

在上一讲中,我们对Lambda表达式做了一个简单的了解,包括见识过了它的语法格式,以及知道了它可以大大简化匿名内部类的代码量,可以更加清晰的展示主要代码,以此增强整体代码的可读性。

接下来,咱们就对Lambda表达式的基础语法进行一个系统性的学习,大家也不要怕难学,Lambda表达式无非就是Java 8提出来的一种新的语法格式,学就完事了,而且在我的带领下,相信大家也能快速学会,学会了之后,也就能灵活运用了。

在Java 8中引入了一个新的操作符,即->,该操作符通常被称为箭头操作符或者Lambda操作符。而且,该箭头操作符还将整个Lambda表达式拆分成了两个部分,一个左侧部分,一个右侧部分。

  • 左侧:指定了Lambda表达式需要的所有参数,对应接口中抽象方法的参数列表。
  • 右侧:指定了Lambda体,即Lambda表达式要执行的功能,对应接口中抽象方法的实现。

现在大家该知道所谓的Lambda表达式就是对接口的实现了吧!之前我们都是使用匿名内部类去实现接口的,而现在我们就可以使用Lambda表达式去实现接口了。

有童鞋肯定想问,如果接口中要是有多个抽象方法,那么Lambda表达式默认实现的是哪个呢?实际上Lambda表达式需要一个函数式接口的支持,所谓的函数式接口就是指只有一个抽象方法的接口,下一讲我就会讲到它,大家这里先知道其概念即可。

接下来,我就要开始讲述Lambda表达式的基础语法了。注意,Lambda表达式一共有好几种变化的形式,下面我总结出来了有六种形式。虽然变化的形式有点多,但是大家也不要怕,跟着我学起来还是比较简单的。

语法格式一:无参数,且无返回值

第一种语法格式指的就是接口中的那个抽象方法无参,并且无返回值。

那么,你能找到这样一个接口吗?即接口中的那一个抽象方法无参,并且无返回值。是不是很容易就能找到这样一个类似的接口啊!Runnable接口就是嘛,你看下Runnable接口的源码就知道了。

这里,我就以Runnable接口为例来讲述这种语法格式。

原来如果我们想要对Runnable接口进行实现,那么是不是得使用匿名内部类的方式来实现该接口啊,就像下面这样。

package com.meimeixia.java8;

import org.junit.Test;

import java.util.*;

/**
 *
 * @author liayun
 * @create 2021-12-09 1:02
 */
public class TestLambda2 

    @Test
    public void test1() 
        Runnable r = new Runnable() 
            @Override
            public void run() 
                System.out.println("Hello World!");
            
        ;

        r.run();
    


现在,匿名内部类就可以使用Lambda表达式来充当了,即我们可以使用Lambda表达式来实现Runnable接口。

package com.meimeixia.java8;

import org.junit.Test;

import java.util.*;

/**
 *
 * @author liayun
 * @create 2021-12-09 1:02
 */
public class TestLambda2 

    @Test
    public void test1() 
        Runnable r = new Runnable() 
            @Override
            public void run() 
                System.out.println("Hello World!");
            
        ;

        r.run();

        System.out.println("---------------------------------------");

        Runnable r1 = () -> System.out.println("Hello Lambda!");
        r1.run();
    


看到了吧,现在使用Lambda表达式来实现Runnable接口,是不可以少写很多代码啊,方便吧!

注意,大家在写Lambda表达式时,有一个小的注意事项要知道一下。相信大家之前在学局部内部类的时候就知道了,就是如果在局部内部类中应用了一个同级别的局部变量,那么在JDK 1.7及之前该变量必须是final的,就像下面这样。

package com.meimeixia.java8;

import org.junit.Test;

import java.util.*;

/**
 *
 * @author liayun
 * @create 2021-12-09 1:02
 */
public class TestLambda2 

    @Test
    public void test1() 
        // 如果在局部内部类中应用了一个同级别的局部变量,那么在JDK 1.7及之前该变量必须是final的
        final int num = 0;
        
        Runnable r = new Runnable() 
            @Override
            public void run() 
                System.out.println("Hello World!" + num);
            
        ;

        r.run();
    


但是在Java 8以后,同级别的局部变量前面的final关键字就可以不用加了,所以你像下面这样写也是可以的。

package com.meimeixia.java8;

import org.junit.Test;

import java.util.*;

/**
 *
 * @author liayun
 * @create 2021-12-09 1:02
 */
public class TestLambda2 

    @Test
    public void test1() 
        // 如果在局部内部类中应用了一个同级别的局部变量,那么在JDK 1.7及之前该变量必须是final的
        /*final*/ int num = 0;
        
        Runnable r = new Runnable() 
            @Override
            public void run() 
                System.out.println("Hello World!" + num);
            
        ;

        r.run();
    


虽然在Java 8以后,同级别的局部变量前面的final关键字可以不用加,但是该局部变量实际上还是一个final的(只不过我们可以省略,默认不写),因此它就不可以做一些操作了,例如自增或者自减。

同理,Lambda表达式也是同样一个道理,大家一定要注意哟!

实际上,Lambda表达式我们也可称之为是语法糖或者糖语法,它底层就是一个函数(方法),只不过,我们写起来比较简单而已。

语法格式二:有一个参数,并且无返回值

第二种语法格式指的就是接口中的那个抽象方法有一个参数,并且无返回值。

那么,你能找到这样一个接口吗?即接口中的那一个抽象方法有一个参数,并且无返回值。其实,Java 8中就有这样一个接口,即Consumer接口,其定义如下。

这里,我就以Consumer接口为例来讲述这种语法格式。

我们可以使用Lambda表达式对Consumer接口中有一个参数的accept方法进行一个实现,如下所示。

@Test
public void test2() 
    Consumer<String> con = (x) -> System.out.println(x);
    con.accept("我是李阿昀!");

语法格式三:若只有一个参数,参数的小括号可以省略不写

第三种语法格式指的就是接口中的那个抽象方法如果只有一个参数,那么参数的小括号就可以省略不写,但习惯上还是写上比较好,因为多写一个小括号又不会死人。

还是以上面的Consumer接口为例来讲述这种语法格式,这时,代码就要这样写了。

@Test
public void test2() 
    // Consumer<String> con = (x) -> System.out.println(x);
    Consumer<String> con = x -> System.out.println(x);
    con.accept("我是李阿昀!");

语法格式四:有两个以上的参数,有返回值,并且Lambda体中有多条语句

第四种语法格式指的就是接口中的那个抽象方法有两个以上的参数,有返回值,并且Lambda体中有多条语句,那么此时Lambda体必须使用大括号括起来。

这里,我就以Comparator接口为例来讲述这种语法格式。

@Test
public void test3() 
    Comparator<Integer> com = (x, y) -> 
        System.out.println("函数式接口");
        return Integer.compare(x, y);
    ;

语法格式五:当Lambda体中只有一条语句时,return和大括号都可以省略不写

第五种语法格式指的就是接口中的那个抽象方法有两个以上的参数,有返回值,并且Lambda体中只有一条语句,那么此时return和大括号都可以省略不写。

这里,我同样以Comparator接口为例来讲述这种语法格式。

@Test
public void test4() 
    Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

语法格式六:Lambda表达式中的参数列表的数据类型可以省略不写,因为JVM的编译器可以通过上下文来推断出数据类型

最后这种语法格式指的就是Lambda表达式中的参数列表的数据类型可以省略不写,因为JVM的编译器可以通过上下文来推断出数据类型,这个过程我们也称之为类型推断。其实,类型推断也是一个语法糖。

原本Lambda表达式中参数列表是应该有数据类型的,就像下面这样。注意,像下面这样写是没有任何问题的哟!

@Test
public void test4() 
    Comparator<Integer> com = (Integer x, Integer y) -> Integer.compare(x, y);

虽然像上面那样写没有任何问题,但是Lambda表达式中参数列表的数据类型是可以省略不写的,而且我们一直也在这样做。

@Test
public void test4() 
    Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

之所以能这样写,就是因为JVM的编译器可以通过上下文来推断出数据类型。

有童鞋可能就要问了,那是如何通过上下文来推断出数据类型的呢?这里,我先说出我的观点,若有不对的地方,大家可以跟我讨论讨论,一起交流一番。

就拿上面的例子来说,Lambda表达式中参数列表的数据类型省略不写,虽然没写,但是前面声明变量的时候泛型是不是指定为了Integer啊!

而我们都知道以上Lambda表达式就是对Comparator接口的一个实现,而且Lambda体就对应着接口中抽象方法的实现。于是,通过接口中抽象方法参数列表的数据类型我们就能推断出Lambda表达式中参数列表的数据类型了。

那这就好办了,刚才我们在声明变量的时候接口泛型是不是指定为了Integer啊,所以接口中抽象方法参数列表的数据类型就是Integer,即T为Integer,这样的话,我们就能推断出Lambda表达式中参数列表的数据类型了。

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

实际上,我们之前就已经用过类型推断,只不过你没意识到而已。

@Test
public void test5() 
    String[] strs = "aaa", "bbb", "ccc"; // 这就是类型推断

像以上这样简单初始化数组,就用到了类型推断,因为后面部分我就没指定类型。那你说类型是哪来的啊?是不是就是通过上下文推断出来的啊!但是,像下面拆分开来写,就不可以了,因为不能通过上下文推断出类型来。

还有在JDK 1.7之后,集合是可以像下面这样写的。

看到没,ArrayList<><>里面的数据类型可以不用写了。那你说数据类型是哪来的啊?是不是也是通过上下文推断得出来的呀!

在Java 8中,对类型推断进一步的升级了,升级成什么样了呢?如下图所示。

小结

这里,给大家做一个总结,大家先看一副对联吧,如下图所示。

能看懂这副对联吧!要是看不懂,我来给大家解释解释。

上联是左右遇一括号省,说的是啥意思呢?说的就是当箭头操作符左侧只有一个参数时,小括号能省略不写;当箭头操作符右侧只有一条语句时,大括号也能省略不写。

下联是左侧推断类型省,说的是啥意思呢?说的就是箭头操作符的左侧有一个类型推断,即可以通过目标上下文推断出参数列表的数据类型,所以箭头操作符左侧参数列表的数据类型就可以省略不写了。

横批就是能省则省。大家现在懂了吧!

以上是关于重学Java 8新特性 | 第4讲——Lambda表达式详解的主要内容,如果未能解决你的问题,请参考以下文章

重学Java 8新特性 | 第4讲——Lambda表达式详解

重学Java 8新特性 | 第3讲——我们为什么要使用Lambda表达式?

重学Java 8新特性 | 第3讲——我们为什么要使用Lambda表达式?

重学Java 8新特性 | 第3讲——我们为什么要使用Lambda表达式?

重学Java 8新特性 | 第5讲——函数式接口

重学Java 8新特性 | 第5讲——函数式接口