概念
Lambda表达式,也称函数编程,百度百科概念:
Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。
从上面的概念上看,Lambda表达式其实就是一个没有名字的函数。但有一个问题,Java并没有函数的这个概念,那JDK8之后所说的函数是啥意思呢?
答案:Java中函数就是函数接口中抽象方法的实现逻辑
再具体点:函数接口抽象方法的方法体
表现形式:
(parameters) -> expression
或
(parameters) ->{ statements; }
以线程Runable接口为例子
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
JDK8之前,普通匿名内部类实现
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(".....");
}
}).start();
JDK8之后,Lambda实现
new Thread(()-> {System.out.println("....");}).start();
Thread类传入的对象就是lambda表达式对象:()-> {System.out.println("....");},这也就概念上讲的Java函数。
如果你认真观察,你就会发现Thread接收参数是Runable接口的run方法实现。
案例分析
需求:定义一个函数接口,使用常规方式、匿名内部类方式与lambda方式实现
定义一个函数接口:
@FunctionalInterface
public interface MyInterface {
void sayHi();
}
方式1:常规的实现方式
//定义一个接口实现类
public class MyInterfaceImpl implements MyInterface{
@Override
public void sayHi() {
System.out.println("hello lambda....");
}
}
public class InterfaceDemo {
//定义函数接口方法
public void hi(MyInterface myInterface){
myInterface.sayHi();
}
public static void main(String[] args) {
InterfaceDemo demo = new InterfaceDemo();
demo.hi(new MyInterfaceImpl());
}
}
方式2:匿名内部类实现方式
public class InterfaceDemo {
//定义函数接口方法
public void hi(MyInterface myInterface){
myInterface.sayHi();
}
public static void main(String[] args) {
InterfaceDemo demo = new InterfaceDemo();
demo.hi(new MyInterface() {
@Override
public void sayHi() {
System.out.println("hello lambda...");
}
});
}
}
方式3:使用Lambda实现方式
public class InterfaceDemo {
//定义函数接口方法
public void hi(MyInterface myInterface){
myInterface.sayHi();
}
public static void main(String[] args) {
InterfaceDemo demo = new InterfaceDemo();
//函数逻辑
demo.hi(()-> System.out.println("hello lambda..."));
}
}
2种方式实现的效果一样,使用lambda方式实现更加简洁方便。同时也有点无厘头,特别是没有学习或完全掌握Lambda语法的同学,说不出的奇怪感。那Lambda究竟是啥回事?
原理
要弄懂这个lambda表达式实现原理,需要借助JDK自带的Java class文件分解器:javap 工具
步骤1:检查java的环境变量
注意:必须配置了JAVA环境变量
步骤2:查看字节码基本信息
进入到项目字节码(.class)文件所在目录,演示案例使用的maven结构项目,所以进入target目录。
//输入命令
javap -p InterfaceDemo.class
观察返回的字节码信息,有个特殊的方法
private static void lambda$main$0();
看到这,可以大胆做推断:当类中使用lambda 表达式,编译器会自动创建一个私有的,静态的,格式为: lambda$方法名$序号 方法。
步骤3:验证 lambda$方法名$序号方法 推断
这里我们可以确认推断是否正确, 在InterfaceDemo 类上加上相同方法签名的方法
public class InterfaceDemo {
//定义函数接口方法
public void hi(MyInterface myInterface){
myInterface.sayHi();
}
public static void main(String[] args) {
InterfaceDemo demo = new InterfaceDemo();
demo.hi(()-> System.out.println("hello lambda..."));
}
//编译前自定义名称一模一样的方法
private static void lambda$main$0(){
}
}
然后执行run操作
编译没问题,但一执行马上抛异常,异常明显指定跟新增的方法有关。
到这,证明上面推断是对的。
步骤4:大胆猜测,小心求证
上面推断是正确的, 那我们不禁要问,lambda$main$0 方法是干什么的,跟lambda表达式有啥关系?回想下,刚刚案例分析中提到实现方式:
常规方式:定义一个新类:MyInterfaceImpl 实现接口MyInterface
匿名内部类方式:定义一个匿名的类,实现接口MyInterface
上面2种都需要一个接口实现类,Lambda表达式能实现相同功能,那是否可以推断lambda表达式的实现方式也必须要有一个接口实现类呢??完全可以取验证一下:
Lamdba 表达方式:
在执行InterfaceDemo 类main方法之前, 先配置VM options,目的:JVM运行时将动态创建类字节码对象打印处理。
-Djdk.internal.lambda.dumpProxyClasses
运行之后得到结果
用idea打开:
import java.lang.invoke.LambdaForm.Hidden;
// $FF: synthetic class
final class InterfaceDemo$$Lambda$14 implements MyInterface {
private InterfaceDemo$$Lambda$14() {
}
@Hidden
public void sayHi() {
InterfaceDemo.lambda$main$0();
}
}
惊讶发现,lambda语法确实创建了一个MyInterface接口实现类,名字为:InterfaceDemo$$Lambda$14,那么
//定义
public void hi(MyInterface myInterface){
myInterface.sayHi();
}
//调用
demo.hi(()-> System.out.println("hello lambda..."));
可以认为等价于
//调用
demo.hi(new InterfaceDemo$$Lambda$14());
步骤5:真相大白
到这,离真相大白就剩下一步之遥了:
最后一个问题,InterfaceDemo$$Lambda$14()类中sayHi方法是怎么重写的?
//实现类:InterfaceDemo$$Lambda$14
@Hidden
public void sayHi() {
InterfaceDemo.lambda$main$0();
}
内部类InterfaceDemo$$Lambda$14重写接口方法sayHi直接调用:InterfaceDemo的lambda$main$0(); 方法
而lambda$main$0 方法是编译器自动生成的。那lambda$main$0 方法的方法体是啥,实现了啥逻辑?
再次使用javap命令查看字节码对象与编译流程(此次查看更详细的编译过程)
javap -p -v -c InteraceDemo.class
这种命令查看信息更全,截取重要的信息
private static void lambda$main$0();
descriptor: ()V
flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String hello lambda...
5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 10: 0
}
上面的代码是类似汇编语言,从旁边的注释上能看出lambda$main$0方法大概实现了。
lambda$main$0 方法在方法体重,调用了PrintStream类的println方法,打印的值是字符:hello lambda... 也即: System.out.println("hello lambda....");
哇塞~ 真相大白了。
步骤6:总结
我们从头梳理一下:
lambda表达式写法
public class InterfaceDemo {
//定义函数接口方法
public void hi(MyInterface myInterface){
myInterface.sayHi();
}
public static void main(String[] args) {
InterfaceDemo demo = new InterfaceDemo();
demo.hi(()-> System.out.println("hello lambda..."));
}
}
编译之后的写法:
public class InterfaceDemo {
//定义函数接口方法
public void hi(MyInterface myInterface){
myInterface.sayHi();
}
//编译器自动添加静态方法,方便动态生成的内部类调用
private static void lambda$main$0(){
System.out.println("hello lambda...");
}
public static void main(String[] args) {
//编译器动态创建的局部内部类
final class InterfaceDemo$$Lambda$14 implements MyInterface {
private InterfaceDemo$$Lambda$14() {
}
@LambdaForm.Hidden
public void sayHi() {
InterfaceDemo.lambda$main$0();
}
}
InterfaceDemo demo = new InterfaceDemo();
demo.hi(new InterfaceDemo$$Lambda$14());
}
}
总结
Lambda表达式实现原理:
1>JDK编译时会给使用lambda表达式的类中添加一个私有的,静态的方法, 格式:lambda$方法名$序号
2>JDK编译时动态创建一个局部内部类,该类实现了函数接口,重写函数接口唯一的抽象方法。
3>局部内部类重写接口抽象方法:直接调用步骤1中新增的私有的,静态的方法。
最后的最后,用大白话讲请求lambda表达式原理:
Lambda表达式就一个语法糖,本质还是常规的接口实现,只是将实现过程做了简化。