序言
JDK 8 日渐成为项目开发中的主流。
但平时在和很多小伙伴的交流和面试中,发现很多人仍停留在 JDK 7 及以前的认知层面,Lambda 表达式、方法引用、Stream 流、default 关键字,很少使用,甚至还有不少小伙伴不知道怎么用!!
不客气地说,不掌握 JDK 8 的新特性,面试通过基本很难很难。换位思考,若不掌握,你面试不慌吗?
本文会帮你详细梳理 JDK 8 中的新特性,有原理讲解,有示例实战,助力你面试起飞。
提前预警,本文很长!但一定是干货满满的,对于技术文章而言,短小精悍的特点并不是好事,因此我写的文章都偏长,注重干货,注重前因后果,做到知其然更要知其所以然。如果你有耐心读下去,一定会有较大收获。
如果没耐心看下去,或没时间看,请直接跳到最后的第四节,可微信公众号收藏留以备用~
一、Lambda 表达式
在 Java 语言中使用 Lambda 表达式,是 JDK 8 推出的最重要特性之一。它能够简化我们的传统操作。
在具体描述 Lambda 表达式之前,我们需要补充一些基础知识:什么是函数式接口。
1.1 函数式接口的定义
提到函数式接口(unctional interface),就牵扯到一个注解:@FunctionInterface。
所谓函数式接口,是指的一类添加了 @FunctionInterface 注解的接口。换言之,只要一个接口有 @FunctionInterface 注解,那这个接口就是函数式接口。
举个例子就明白了。
当你在对任务 taskA 处理时,如果想异步处理,不影响主干流程的继续进行,你会怎么做?
初级版:新增一个类,实现 Runnable 接口
你会说很简单呐,另起一个线程去执行任务 taskA 就可以了呀,喏,如下:
/**
* @author: sss
*/
public class TaskAThread implements Runnable {
@Override
public void run() {
// process taskA
...
}
}
public class Main {
public static void main(String[] args) {
// new 一个新线程,执行任务A
Runnable taskA = new TaskAThread();
new Thread(taskA).start();
// 主线程继续做其他事情
System.out.println("do other things...");
}
}
这种方式是可以实现,但有没有其他方式呢?
进阶版:使用匿名内部类
有些小伙伴明显的发现了上面代码中的问题:繁琐!!只是为了创建一个线程并使用它的 run() 方法,还要新增一个类,没有必要,直接使用匿名类就解决啦:
public class Main {
public static void main(String[] args) {
// 通过匿名类来创建一个新线程,执行任务A
Runnable taskA = new Runnable() {
@Override
public void run() {
// process taskA
...
}
};
new Thread(taskA).start();
// 主线程继续做其他事情
System.out.println("do other things...");
}
}
通过匿名类的方式,省去了新增一个类的操作,大大简化。但若使用 Lambda 的方式,会更加简洁。
高级版:使用 Lambda 表达式
public static void main(String[] args) {
// 通过匿名类来创建一个新线程,执行任务A
new Thread(() -> {
System.out.println("正在异步处理 taskA 中...");
// do things
...
}).start();
// 主线程继续做其他事情
System.out.println("do other things...");
}
有没有发现很神奇,类似 () -> {...} 的这种箭头式写法竟然能通过编译!而且还能运行(不信的小伙伴可以试试)!这种就是 Lambda 表达式的其中一种写法,不理解的小伙伴也没关系,我们后面会详细解释。
也许这种 Lambda 写法很多小伙伴见过,并习以为常,但为什么可以运行,你知道根本原因吗?
这里就体现出函数式接口的作用了。我们去看一下 JDK 7 和 JDK 8 中关于 Runnable 接口的定义,如下。大家有发现什么不同点了吗?
眼尖的小伙伴一定发现了,JDK 8 中多了个注解 @FunctionalInterface。这就是为何能在 JDK 8 中可以使用这种箭头式的 Lambda 写法。
本小节最开始时我们也提到了此注解。从上图也能看出,@FunctionalInterface 是JDK 8 中新引入的一个注解,它定义了一类新的接口(即函数式接口),该类接口有且只能有一个抽象方法。
它主要用于编译期的错误检查,如果一个接口不包含抽象方法(Serializable、Cloneable 等标记接口),或者包含多个抽象方法,都不符合 @FunctionalInterface 注解的定义,加了就会出错,如下这种:
// 错误示例 1
@FunctionalInterface
interface InvalidInterfaceA {
}
// 错误示例 2
@FunctionalInterface
interface InvalidInterfaceB {
void testA();
void testB();
}
正确示范:
@FunctionalInterface
interface InvalidInterfaceC {
void testC();
}
@FunctionalInterface
interface InvalidInterfaceD {
void testD();
default void testE() {
System.out.println("this is a default method.");
}
}
@FunctionalInterface 修饰的接口,只能有一个抽象方法,但并代表只能有一个方法声明,像上面的 InvalidInterfaceD 接口,还有 default 关键字修饰的 testE() 方法,但这是一个有默认实现的方法,并不是抽象方法,因此接口 InvalidInterfaceD 依然符合函数式接口的定义。