Java8之lambda表达式
简介
Lambda 表达式是 Java 1.8 跟 Stream 机制一同推出的。Lambda 表达式极大地减少了代码量,增加了代码的可读性。
引入 Lambda 表达式之后,Java 开始支持把函数作为参数传递。
前置条件
使用 Lambda 表达式的前置条件,作为参数的接口必须是函数式接口
- 首先类型必须是接口
interface
,不能是类class
。比如,抽象类就不可以。 - 函数式接口有且仅有一个未被覆写的抽象方法
举例:
- Object 中方法不算
// MyRunnable 仍然算是一个“函数式接口”
public interface MyRunnable extends Runnable {
String toString();
boolean equals(Object obj);
int hashCode();
}
- 接口中的 default 方法不算
// MyRunnable2 仍然算是一个“函数式接口”
public interface MyRunnable2 extends Runnable {
default void run2() {}
}
FunctionalInterface 注解
@FunctionalInterface
可以帮助我们在编译期识别出一个接口是否是“函数式接口”:
参数的传递
假如我们有一个如下含义的“函数式接口”:
@FunctionalInterface
public interface Formatter {
void format(String name, int age);
}
我们可以构造一个测试:
public class LambdaTest {
public static void main(String[] args) {
print((String name, int age)-> System.out.println(String.format("name:%s age:%d", name, age)), "ziyu", 18);
}
public static void print(Formatter formatter, String name, int age) {
formatter.format(name, age);
}
}
多个参数
当有多个参数时,可以选择省略所有参数类型声明:
注意:
不能省略一部分保留一部分。(String name, age) -> System.out.println(name); 这是不合法的!
单个参数
当只有一个参数时,除了可以省略参数类型,还可以进一步省略掉括号。
编写方式
没有返回值
Runnable
就是一个常用的“函数式接口”,它的抽象方法 run()
“没有返回值”, 刚好适合用于此处的演示。测试例子如下:
public class LambdaTest2 {
public static void main(String[] args) {
runIt(()->{
System.out.println("123");
});
}
static void runIt(Runnable runnable) {
new Thread(runnable).start();
}
}
- 如果写成多行表达式,那么需要
{}
来表示代码块,且每一行代码结束时需要书写;
表示语句的结束。 - 如果代码块中只有一条语句,那么可以通过省略
{}
和;
来简写为单行表达式
有返回值
我们定义一个 IdFactory
接口来做演示:
public interface IdFactory {
String generateId();
}
我们的示例代码如下:
import java.util.UUID;
public class LambdaTest3 {
public static void main(String[] args) {
String name = getId(()-> UUID.randomUUID() + "");
System.out.println(name);
}
static String getId(IdFactory factory) {
return factory.generateId();
}
}
- 如果写成多行表达式,除了需要
{}
来表示代码块,和每一行代码结束时需要书写;
表示语句的结束以外,还应该在需要返回值的方法用return
来返回值。 - 如果代码块中只有一条语句,那么可以通过省略
{}
和;
以及return
来简写为单行表达式
方法引用
比如我们要写一段代码,用来打印出完整的加法表达式 a + b = c
,并且要求根据 a 和 b 求出 c 作为函数返回值。
首先我们定一个“计算器”接口:
public interface Calculator {
int compute(int a, int b);
}
接着我们写一个测试用例:
public class LambdaTest3 {
public static void main(String[] args) {
int a = 10;
int b = 20;
// 这里不能用 (a, b), 那样的话会产生歧义,使得编译器报错
runIt((x, y)->{
System.out.print(x);
System.out.print(" + ");
System.out.print(y);
System.out.print(" = ");
System.out.println(x + y);
return x + y;
}, a, b);
}
static void runIt(Calculator calculator, int a, int b) {
calculator.compute(a, b);
}
}
静态方法引用
- 在 LambdaTest2 中定一个静态方法
sum
- 使用静态方法引用
LambdaTest2::sum
- 这样在运行静态方法
runIt
中的第一行calculator.compute(a, b)
时,a
和b
会通过静态方法引用传递给静态方法sum
通过这种方式,即简化了参数的传递,也把“多行表达式”简化为了“单行表达式”
成员方法引用
new LambdaTest2()::sum
: 先新建对象LambdaTest2
,并且通过该对象来使用成员变量引用
小贴士:
如果先声明变量obj
,再使用obj::sum
也是合法的。
public static void main(String[] args) {
int a = 10;
int b = 20;
LambdaTest2 obj = new LambdaTest2();
runIt(obj::sum, a, b);
}
总结
- Lambda表达式的前置条件:必须是“函数式接口”
- 单个参数传递时,可以省略参数两端的括号。参数的类型可以一起省略。
- 编写的方式主要包括单行表达式和多行表达式
- 可以使用方法引用来把多行表达式写成单行表达式。方法引用又包括了静态方法引用和动态方法引用。