1. 简介
Lambda表达式体现的是一种函数式的编程思想,区别于之前的匿名内部类,Lambda表达式只需要将要执行的代码放到一个函数中,函数就是类中的方法,Lambda表达式就是一个匿名函数,所以我们只需要将要执行的代码放到Lambda表达式中。
使用Lambda可以简化匿名内部类,让代码跟精简,同时,运行效率更高。
我们可以这么认为,Lambda表达式就是对接口中抽象方法的重写,所以,今后我们看到函数的参数是接口就可以考虑使用Lambda表达式。
2 Lambda的语法
参数列表(多个参数以逗号分隔,参数用括号括起来)+箭头标记+主体部分
左侧:Lambda 表达式的参数列表
右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体
lambda有以下6种常见的语法格式:
语法格式一:无参数,无返回值
() -> System.out.println("Hello Lambda!");
语法格式二:有一个参数,并且无返回值
(x) -> System.out.println(x)
语法格式三:若只有一个参数,小括号可以省略不写
x -> System.out.println(x)
语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
(Integer x, Integer y) -> Integer.compare(x, y);
2.1 标准格式
(参数类型 参数名称) -> {代码体;}
格式说明:
1) ( 参数类型 参数名称 ):参数列表
2){代码体;}:方法体
3)-> :箭头,分隔参数列表和方法体,不能有空格。
例如:对于比较器的写法
// 之前的写法
/*
Comparator<Apple> comparators = new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getColor().compareTo(o2.getColor());
}
};
*/
// 新写法
Comparator<Apple> comparator = (Apple a1,Apple a2) -> a1.getColor().compareTo(a2.getColor());
注意:当主体部分为单语句体的时候,大括号可以省略,这时候,返回值可以不用写。
2.2 lambda的省略格式
规则:
1)小括号内参数的类型可以省略
2)如果小括号内有且仅有一个参数,则小括号可以省略
3)如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字以及语句分号(注意:三个必须同时省略)
2.2.1 参数类型可推断
如果参数的类型可以根据上下文推断出来,则可以省略掉类型,
例如:上面的比较器,Apple可以省略
Comparator<Apple> comparator = (a1,a2) -> a1.getColor().compareTo(a2.getColor());
2.2.2 存在多行代码
如果主体部分是多表达式的形式,那么需要用大括号括起来
Comparator<Apple> comparator = (a1,a2) -> {
if (a1.getColor().length() > a2.getColor().length()) {
return 1;
}else{
return -1;
}
};
2.2.3 单个参数并可推断类型
如果只有一个参数,并且参数的类型可以推断,那么可以省略括号()
Function<String, Integer> stringIntegerFunction = s -> s.length();
2.2.4 没有参数
() -> {for(int i=0; i<10; i++) {
doSomthing();
}
}
3. 实现原理
3.1 探究过程
用反编译工具无法反编译带有Lambda表达式的class文件,这时候我们需要使用java自带的反编译工具javap指令
在dos命令行输入:
javap -c -p 文件名.class
-c 表示对代码进行反汇编
-p 显示所有类和成员
通过反编译class文件,可以知道,Lambda表达式会在这个类中新生成一个私有的静态方法,该方法命名规则为: lambda$表达式所在方法名$第几个表达式
例如:lambda$main$0
该静态方法的函数体就是lambda表达式中的body。
那么,如何调用这个方法了?其实lambda在运行的时候会生成一个内部类,为了验证是否生成了内部类,可以在运行时加上 如下命令:java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名
例如:
注意:这里要在编译之后的classes目录下执行
执行成功之后,在查看有没有生产类文件,如图,发现是生成了的
通过反编译工具,打开这个类文件,发现,lambda生成的类文件就是实现了接口并覆写了接口方法,然后在方法中调用了新增的静态方法。将类文件还原成内部类的形式,就是如图所示:
3.2 小结
1. 匿名内部类是在编译的时候生成一个class文件
2. lambda表达式是在运行的时候,生成一个class文件
1)在类中新增一个方法,这个方法的方法体就是lambda表达式的body
2)还会形成一个匿名内部类,实现接口,重写抽象方法
3)在接口的重写方法中会调用新生成的方法
所以,lambda表达式就是对接口的实现并重写了其抽象方法。
4. 使用Lambda表达式的前提条件
1)方法参数或类中的变量类型必须为接口才能使用lambda
2)接口中有且仅有一个抽象方法
只有一个抽象方法的接口也称之为函数式接口,为了防止开发中误将函数式接口多定义了抽象方法,JDK给我们提供了一个注解@FunctionalInterface来检测这个接口是不是只有一个抽象方法,如果不是,编译不通过。
5. Lambda表达式与匿名内部类的区别
5.1 所需类型不一样
匿名内部类:需要的类型可以是类,抽象类或者接口
Lambda表达式:需要的类型必须是接口
5.2 抽象方法数量不一样
匿名内部类:所需的接口中抽象方法的个数不限
Lambda表达式:所需的接口只能有一个抽象方法
5.3 实现原理不同
匿名内部类:在编译后会形成class
Lambda表达式:是在程序运行的时候动态生成class
小结:当接口中国只有一个抽象方法的时候,建议使用Lambda表达式,其他情况,还是需要使用匿名内部类
6 Lambda变量作用域
Lambda表达式只能引用外部被final修饰的局部变量,换句话说在Lambda表达式中我们不能修改定义在外的局部变量。
public class LambdaTest {
public static void main(String[] args) {
//final如果不写的话,Lambda表达式会默认该变量为final
final String string="Hello ";
HelloWorld test = (String message)->System.out.println(message+"World!");
test.print(string);
}
@FunctionalInterface
interface HelloWorld {
void print(String str);
default void print2() {
}
}
}