Lambda表达式,又可称为闭包,是Java 8中的一项重要特性,本质上属于函数式编程的一种。
理解Lambda表达式的关键,是理解函数式接口(functional interface)的概念。所谓函数式接口,就是指该接口内只包含唯一一个抽象方法。对于函数式接口,我们可以通过Lambda表达式创建该接口的对象。
一、Lambda表达式的引出
1. 我们先看一下如何定义一个函数式接口,定义类以实现该接口,并创建类的对象以调用相关方法。
public class Main {
public static void main(String[] args) {
// 1.3 Create a object
Animal animal = new Rabbit();
// 1.4 Call the method
animal.eat();
}
}
// 1.1 Define a functional interface
interface Animal {
void eat();
}
// 1.2 Define a class which implements the interface
class Rabbit implements Animal {
@Override
public void eat() {
System.out.println("Grass");
}
}
2. 可以看到,上面传统的定义方式十分繁琐。于是,我们可以采用静态内部类予以简化。
public class Main {
// 2.2 Define a static class which implements the interface
static class Rabbit implements Animal {
@Override
public void eat() {
System.out.println("Grass");
}
}
public static void main(String[] args) {
// 2.3 Create a object
Animal animal = new Rabbit();
// 2.4 Call the method
animal.eat();
}
}
// 2.1 Define a functional interface
interface Animal {
void eat();
}
这段程序与上面的不同之处在于类定义的位置不同了。现在,我们将类定义搬到了另一个类Main内,故称之为“内部类”,此外由在前面增加了一个修饰符static(即“静态”),这样Main类内的main方法才可以创建Rabbit对象。
3. 上述方法要在一个类里再创建一个类,我们还可以对其再进行修改。现在,我们使用局部内部类编写程序。
public class Main {
public static void main(String[] args) {
// 3.2 Define a local class which implements the interface
class Rabbit implements Animal {
@Override
public void eat() {
System.out.println("Grass");
}
}
// 3.3 Create a object
Animal animal = new Rabbit();
// 3.4 Call the method
animal.eat();
}
}
// 3.1 Define a functional interface
interface Animal {
void eat();
}
这段代码把Rabbit类的定义搬到了main方法内部,所以称为“局部内部类”。
4. 不论是静态内部类,还是成员内部类,写起来都还是太复杂。于是,我们还可以再做简化,利用匿名内部类实现。
public class Main {
public static void main(String[] args) {
// 4.2 Create a object which define an anonymous class
Animal animal = new Animal() {
@Override
public void eat() {
System.out.println("Grass");
}
};
// 4.3 Call the method
animal.eat();
}
}
// 4.1 Define a functional interface
interface Animal {
void eat();
}
这段代码利用内部类创建了一个没有名称类的对象(故称为“匿名”),并进一步完成了对类内方法的重写,从而实现了Animal接口。
5. 事实上,匿名内部类还是有些繁琐。从Java 8开始出现了一种新的特性——Lambda表达式,可以帮助我们进一步简化该步骤。
public class Main {
public static void main(String[] args) {
// 5.2 Create a object by lambda expression
Animal animal = () -> {
System.out.println("Grass");
};
// 5.3 Call the method
animal.eat();
}
}
// 5.1 Define a functional interface
interface Animal {
void eat();
}
在这个Lambda表达式中,圆括号()对应前面Rabbit类中eat()方法的参数列表(当然这里是无参方法,所以就是空的),箭头后面的一对花括号对应eat()方法的方法体。
二、Lambda表达式的语法规则
Lambda表达式有两种形式:
- Statement Lambda:
( parameters ) -> { statements; }
- Expression Lambda:
( parameters ) -> expression
以下是几个例子:
1. 接收两个int型整数,并分别打印它们的和与差。
(int x, int y) -> {
System.out.println(x + y);
System.out.println(x - y);
}
当然,前件中的类型声明也可以都略去:
(x, y) -> {
System.out.println(x + y);
System.out.println(x - y);
}
注意:类型声明如要省略则所有变量都得省略,不能只省略其中一部分,例如下面的代码就是错误的。
(int x, y) -> {
System.out.println(x + y);
System.out.println(x - y);
}
此外,假如后件的语句块中只包含一条语句,则花括号可以省略。
(x, y) -> System.out.println(x + y)
2. 接收2个参数(数字),并返回它们的和。
(x, y) -> x + y
注意,若后件为一条表达式,则可以视作返回值,或者说是语句“return x + y”。
3. 接收1个参数(数字),并返回它的平方。
(x) -> x * x
当然,当参数仅有一条时,括号可以略去。
x -> x * x
下面总结一下:
- Lambda表达式使用的前提是,接口为函数式接口,即其中只包含唯一一个抽象方法。
- 参数的类型声明可以省略,但注意如要省略则所有参数都不应声明类型。
- 若参数只有一个,则可以省略圆括号。
- 若方法块内只有一条语句/仅为一条表达式,则可以省略花括号。
三、Lambda语句与线程创建
事实上,Java中的Runnable接口就是一个函数式接口,其中只包含一个抽象方法run()。显然,我们可以利用Lambda语句减少我们创建线程的工作量。
曾经,我们先定义一个类实现Runnable接口,然后在创建该类的对象,丢入Thread类创建线程对象,最后调用线程对象的start()方法。
public class Main {
public static void main(String[] args) {
new Thread(new Task()).start();
}
}
class Task implements Runnable {
@Override
public void run() {
System.out.println("I love Java.");
}
}
现在,我们可以巧妙利用Lambda表达式特性,简化上述流程。
public class Main {
public static void main(String[] args) {
new Thread(() -> System.out.println("I love Java.")).start();
}
}
于是这样我们只需要一行即可!Thread类构造方法中的Lambda表达式中,前件圆括号对应上面Task类中run()方法的参数列表(当然这里为空),后件对应Task类中run()方法的方法体(因为只有一条语句,所以省略了花括号)。