重学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表达式?