JDK新特性(一)——Lambda表达式
前言
Lambda表达式也可称为“闭包”,是jdk1.8发布的新特性之一,允许把函数作为一个方法的参数进行传递。可以简单的将其理解为是更加简洁的匿名内部类写法,在一些场景中Lambda表达式的写法可以让我们的代码变得更加简洁和清爽,本篇文章中将对Lambda表达式的使用方式、注意事项和与匿名内部类的对比进行介绍,希望能够给各位读者一个参考。
一、Lambda表达式的小例子
在介绍Lambda表达式之前,我们不妨先看一个小例子:
我们当前有一个小需求,要求新建一个线程,然后在线程中简单的输出一句话。
这当然是小问题,创建一个实现Runnable 接口的子类,然后调用一下就行
public class MyRunnableImpl implements Runnable {
@Override
public void run() {
System.out.println("新开启了一个线程...");
}
}
public class LambdaTest {
public static void main(String[] args) {
// 通过实现runnable接口新建线程
// 方式1:
MyRunnableImpl runnableImpl = new MyRunnableImpl();
new Thread(runnableImpl).start();
}
}
但为了一个简单的需求专门创建一个类似乎很麻烦,能不能有更简便的方式呢?
当然有,这不是还有匿名内部类吗!!
public class LambdaTest {
public static void main(String[] args) {
// 通过实现runnable接口新建线程
// 方式2: 使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新开启了一个线程...");
}
}).start();
}
}
这么一看,匿名内部类确实比新创建一个子类要方便得多,但是不得不说,匿名内部类的出现让代码的可读性变得比较差,而且我们真正实现的功能也只是打印一句话,使用匿名内部类的方式还是要写很多代码,还有更加简便的写法吗?
Lambda表达式就是在这种背景下出现的,我们先看一下使用Lambda表达式的写法可以怎么样来解决这个问题
public class LambdaTest {
public static void main(String[] args) {
// 通过实现runnable接口新建线程
// 方式2: 使用Lambda表达式
new Thread(() -> {
System.out.println("新开启了一个线程...");
}).start();
}
}
我们可以看到,使用Lambda表达式之后,代码变得简洁了不少。这就是Lambda表达式的最大的作用,在某些场景下更简洁地书写代码。
这其实就是一种函数式编程的思想,尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
二、Lambda的使用
(一)Lambda表达式的格式
(形式参数) -> { 代码块 }
形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
->:由英文中画线和大于符号组成,固定写法。代表指向动作
代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
我们可以看到,Lambda表达式的书写格式十分简单,满足形式参数、箭头、代码块三个要素即可。
(二)Lambda表达式的小练习
下面,我们就用几个小练习来熟悉一下Lambda表达式吧
练习一(接口方法有参带返回值):使用Lambda
表达式实现下面接口
public interface Caculator {
int add(int a , int b);
}
实现方法如下:
public class LambdaTest {
public static void main(String[] args) {
// 使用匿名内部类的方式解决
getSum(new Caculator() {
@Override
public int add(int a, int b) {
return a + b;
}
},10,20);
// 使用Lambda表达式方式解决
getSum((int a,int b) -> {
return a + b;
},10,20);
}
static void getSum(Caculator caculator,int a , int b){
System.out.println(a+" + "+b + " = "+caculator.add(a, b));
}
}
练习二(接口方法有参不带返回值):使用Lambda
表达式实现下面接口
public interface Student {
void study(String bookName);
}
实现方法如下:
public class LambdaTest {
public static void main(String[] args) {
// 方式1 : 使用匿名内部类的方式实现
/*Student stu = new Student() {
@Override
public void study(String bookName) {
System.out.println("the student is reading "+ bookName);
}
};*/
// 方式二: 使用 Lambda
Student stu = (String bookName) -> {
System.out.println("the student is reading "+ bookName);
};
stu.study("三国演义");
}
}
三、Lambda表达式的省略模式
其实在上面的使用中,还不是Lambda表达式最简洁的写法,在某些情况下,我们还可以对代码进行进一步的省略。
我们先看一下省略的规则:
规则一: 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
我们可以这么理解,接口中仅有一个待实现方法,那么编译器就可以自动识别出传递的参数到底是属于什么类型,我们就没有必要在定义的时候进行类型声明。
我们以第二节练习二的例子作为演示,在使用Lambda表达式声明实现类的时候,不需要声明参数的类型。
public class LambdaTest {
public static void main(String[] args) {
Student stu = ( bookName) -> {
System.out.println("the student is reading "+ bookName);
};
stu.study("三国演义");
}
}
规则二:如果参数有且仅有一个,那么小括号可以省略
上面的例子可以进一步简化为:
public class LambdaTest {
public static void main(String[] args) {
Student stu = bookName -> {
System.out.println("the student is reading "+ bookName);
};
stu.study("三国演义");
}
}
规则三:如果代码块的语句只有一条,可以省略大括号和分号,和return关键字(如果有返回值的话 )
上面的例子可以再简化为:
public class LambdaTest {
public static void main(String[] args) {
Student stu = bookName -> System.out.println("the student is reading "+ bookName);
stu.study("三国演义");
}
}
经过上面的省略规则,我们可以看到现在的式子已经特别简洁了,这就是Lambda表达式的力量所在。
四、Lambda表达式的注意事项
1.使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
原因也很好理解,假如说接口定义了两个无参也无返回值的方法,那么当你使用Lambda表达式创建接口的实现类时,编译器判断不出来你现在写的代码是属于哪个方法中的。
2. 必须有上下文环境,才能推导出Lambda对应的接口
根据局部变量的赋值得知Lambda对应的接口:
Runnable r = () -> System.out.println("Lambda表达式");
根据调用方法的参数得知Lambda对应的接口:
new Thread(() -> System.out.println("Lambda表达式")).start();
其实简单来说,就是你不可以无缘无故写段Lambda表达式,这样编译器也不知道你想干嘛。
五、Lambda表达式和匿名内部类的区别
1.所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
这里也可以说是Lambda表达式的场景属性了,我们可以这么理解,Lambda表达式本身存在的意义之一就是为了让我们书写单方法接口的时候可以更加简洁,而抽象类和实体类内部往往都不止一个(抽象)方法,缺少应用Lambda表达式的场景。所以Lambda表达式在得到简洁性的同时不可避免地失去了适用性。所以在适配场景上上,Lambda表达式是不如匿名内部类的。
2.使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
这个没啥好说的,属于Lambda表达式的局限性之一。
3.实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
写在最后
理解Lambda表达式和匿名内部类的区别对于刚刚接触Lambda写法的人会挺有帮助的哈哈,本篇文章对于Lambda表达式的使用就介绍到这里了,Lambda表达式更为强大的功能还在于和Stream流的配合使用,我们会在后面的文章中提到。
最后再附上一篇我见过对Lambda表达式最通俗易懂的解释:
Lambda 表达式有何用处?如何使用?:
https://www.zhihu.com/question/20125256/answer/324121308