聊一下JDK8中,Lambda表达式实现原理

概念

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表达式就一个语法糖,本质还是常规的接口实现,只是将实现过程做了简化。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浪飞yes

我对钱没兴趣~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值