9.1Lambda表达式
Lambda 表达式(Lambda expression)是一个匿名函数,基于数学中的λ演算得名,也可称为闭包(Closure)
主要作用是不需要创建对象实例,可直接运行方法体
public class Test{
public static Calculable calculate(char opr) {
Calculable result;
if (opr == '+') {
// 匿名内部类实现Calculable接口
result = new Calculable() {
// 实现加法运算
@Override
public int calculateInt(int a, int b) {
return a + b;
}
};
} else {
// 匿名内部类实现Calculable接口
result = new Calculable() {
// 实现减法运算
@Override
public int calculateInt(int a, int b) {
return a - b;
}
};
}
return result;
}
public static Calculable calculate(char opr) {
Calculable result;
if (opr == '+') {
// Lambda表达式实现Calculable接口
result = (int a, int b) -> {
return a + b;
};
} else {
// Lambda表达式实现Calculable接口
result = (int a, int b) -> {
return a - b;
};
}
return result;
}
}
格式
(参数列表) -> {
// Lambda表达式体
}
->
被称为箭头操作符或 Lambda 操作符,箭头操作符将 Lambda 表达式拆分成两部分:
- 左侧:Lambda 表达式的参数列表。
- 右侧:Lambda 表达式中所需执行的功能,用
{ }
包起来,即 Lambda 体
优缺点
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- Java 引入 Lambda,改善了集合操作(引入 Stream API)
缺点:
- 代码可读性变差
- 在非并行计算中,很多计算未必有传统的 for 性能要高
- 不容易进行调试
三种简写方式
①省略参数类型
Lambda 表达式可以根据上下文环境推断出参数类型
public static Calculable calculate(char opr) {
Calculable result;
if (opr == '+') {
// Lambda表达式实现Calculable接口
5 result = (a, b) -> {
return a + b;
};
} else {
// Lambda表达式实现Calculable接口
10 result = (a, b) -> {
return a - b;
};
}
return result;
}
②省略参数小括号
public static Calculable calculate(int power) {
Calculable result;
if (power == 2) {
// Lambda表达式实现Calculable接口
// 标准形式
25 result = (int a) -> {
return a * a;
};
} else {
// Lambda表达式实现Calculable接口
// 省略形式
31 result = a -> {
return a * a * a;
};
}
return result;
}
③省略return和大括号
如果 Lambda 表达式体中只有一条语句,那么可以省略 return 和大括号
public static Calculable calculate(int power) {
Calculable result;
if (power == 2) {
// Lambda表达式实现Calculable接口
// 标准形式
result = (int a) -> {
return a * a;
};
} else {
// Lambda表达式实现Calculable接口
// 省略形式
12 result = a -> a * a * a;
}
return result;
}
三种使用方式
①作为参数
Lambda 表达式一种常见的用途就是作为参数传递给方法,这需要声明参数的类型声明为函数式接口类型
public static void main(String[] args) {
int n1 = 10;
int n2 = 5;
// 打印加法计算结果
display((a, b) -> {
return a + b;
}, n1, n2);
// 打印减法计算结果
display((a, b) -> a - b, n1, n2);
}
public static void display(Calculable calc, int n1, int n2) {
System.out.println(calc.calculateInt(n1, n2));
}
②访问变量
Lambda 表达式可以访问所在外层作用域定义的变量,包括成员变量和局部变量。
访问成员变量
成员变量包括实例成员变量和静态成员变量。在 Lambda 表达式中可以访问这些成员变量,此时的 Lambda 表达式与普通方法一样,可以读取成员变量,也可以修改成员变量
public class LambdaDemo {
// 实例成员变量
private int value = 10;
// 静态成员变量
private static int staticValue = 5;
// 静态方法,进行加法运算
public static Calculable add() {
Calculable result = (int a, int b) -> {
// 访问静态成员变量,不能访问实例成员变量
staticValue++;
int c = a + b + staticValue;
// this.value;
return c;
};
return result;
}
// 实例方法,进行减法运算
public Calculable sub() {
Calculable result = (int a, int b) -> {
// 访问静态成员变量和实例成员变量
staticValue++;
this.value++;
int c = a - b - staticValue - this.value;
return c;
};
return result;
}
}
访问局部变量
对于成员变量的访问 Lambda 表达式与普通方法没有区别,但是访问局部变量时,变量必须是 final 类型的(不可改变)
public class LambdaDemo {
// 实例成员变量
private int value = 10;
// 静态成员变量
private static int staticValue = 5;
// 静态方法,进行加法运算
public static Calculable add() {
// 局部变量
int localValue = 20;
Calculable result = (int a, int b) -> {
// localValue++;
// 编译错误
int c = a + b + localValue;
return c;
};
return result;
}
// 实例方法,进行减法运算
public Calculable sub() {
// final局部变量
final int localValue = 20;
Calculable result = (int a, int b) -> {
int c = a - b - staticValue - this.value;
// localValue = c;
// 编译错误
return c;
};
return result;
}
}
方法引用
方法引用可以理解为 Lambda 表达式的快捷写法,它比 Lambda 表达式更加的简洁,可读性更高,有很好的重用性。
如果实现比较简单,复用的地方又不多,推荐使用 Lambda 表达式,否则应该使用方法引用。
Java 8 之后增加了双冒号::
运算符,该运算符用于“方法引用”,注意不是调用方法。“方法引用”虽然没有直接使用 Lambda 表达式,但也与 Lambda 表达式有关,与函数式接口有关。 方法引用的语法格式如下:
ObjectRef::methodName
//其中,ObjectRef 是类名或者实例名,methodName 是相应的方法名
注意:被引用方法的参数列表和返回值类型,必须与函数式接口方法参数列表和方法返回值类型一致
public class LambdaDemo {
// 静态方法,进行加法运算
// 参数列表要与函数式接口方法calculateInt(int a, int b)兼容
public static int add(int a, int b) {
return a + b;
}
// 实例方法,进行减法运算
// 参数列表要与函数式接口方法calculateInt(int a, int b)兼容
public int sub(int a, int b) {
return a - b;
}
}
public class HelloWorld {
public static void main(String[] args) {
int n1 = 10;
int n2 = 5;
// 打印加法计算结果
display(LambdaDemo::add, n1, n2);
LambdaDemo d = new LambdaDemo();
// 打印减法计算结果
display(d::sub, n1, n2);
}
public static void display(Calculable calc, int n1, int n2) {
System.out.println(calc.calculateInt(n1, n2));
}
}
/*
代码第 18 行声明 display 方法,第一个参数 calc 是 Calculable 类型,它可以接受三种对象:Calculable 实现对象、Lambda 表达式和方法引用。代码第 6 行中第一个实际参数LambdaDemo::add是静态方法的方法引用。代码第 9 行中第一个实际参数d::sub,是实例方法的方法引用,d 是 LambdaDemo 实例;
代码第 6 行的LambdaDemo::add和第 9 行的d::sub是方法引用,此时并没有调用方法,只是将引用传递给 display 方法,在 display 方法中才真正调用方法
*/
9.2函数式接口
Lambda 表达式实现的接口不是普通的接口,而是函数式接口。如果一个接口中,有且只有一个抽象的方法(Object 类中的方法不包括在内),那这个接口就可以被看做是函数式接口。这种接口只能有一个方法。如果接口中声明多个抽象方法,那么 Lambda 表达式会发生编译错误:
这说明该接口不是函数式接口,为了防止在函数式接口中声明多个抽象方法,Java 8 提供了一个声明函数式接口注解 @FunctionalInterface,示例代码如下。
// 可计算接口
@FunctionalInterface
public interface Calculable {
// 计算两个int数值
int calculateInt(int a, int b);
}
在接口之前使用 @FunctionalInterface 注解修饰,那么试图增加一个抽象方法时会发生编译错误。但可以添加默认方法和静态方法。
@FunctionalInterface 注解与 @Override 注解的作用类似。Java 8 中专门为函数式接口引入了一个新的注解 @FunctionalInterface。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
提示:Lambda 表达式是一个匿名方法代码,Java 中的方法必须声明在类或接口中,那么 Lambda 表达式所实现的匿名方法是在函数式接口中声明的