Lambda表达式与匿名内部类的区别分析

21 篇文章 0 订阅
2 篇文章 0 订阅

一、 匿名内部类
匿名内部类其实就是没有名字的内部类,但是其必须要实现一个接口或者继承一个父类,通常是用来简化代码

例程如下:
先定义一个 IAnimal

public interface IAnimal {

    void run();
    void walk();

}

然后定义测试类 NestedClassTest

public class NestedClassTest {

    public static void main(String[] args) {

        IAnimal pig = new IAnimal() {

            @Override
            public void run() {

                System.out.println("pig is running");

            }

            @Override
            public void walk() {

                System.out.println("pig is walking");

            }

        };
        pig.run();
    }

}

进入classes目录,我们可以发现出现了三个class文件:
IAnimal.class
NestedClassTest.class
NestedClassTest$1.class

显然 NestedClassTest$1就是匿名内部类了,现在使用javap指令反编译NestedClassTest.class可以得到如下字节码

public class com.alios.d.NestedClassTest {

  public static void main(java.lang.String[]);

    Code:

       0: new           #2               // 解析运行时常量池中NestedClassTest$1的类符号引     
                                              用,创建对象,并将对象引用推入栈顶

       3: dup                            // 复制栈顶变量

       4: invokespecial #3               // 调用NestedClassTest$1."<init>"方法

       7: astore_1

       8: aload_1

       9: invokeinterface #4,  1         // 调用接口方法IAnimal.run

      14: retur

}

从上文的字节码可以看出,匿名内部类的调用方法与普通类没有区别
* 编译器会为每个匿名类生成一个新的class文件,文件名格式为className$num。

如果Lambda表达式也采用类似方式,将是极为不利的。
* 类加载需要有加载、验证、准备、解析、初始化等过程,大量的内部类将会影响应用执行的性能,并消耗Metaspace

*二、 Lambda表达式*
接下来,我们看一下Lambda表达式
先上代码,我们先定义一个函数式接口

@FunctionalInterface

public interface IBird {

    void fly();

}

然后定义一个Lambda表达式的测试类

public class LambdaTest {

    public static void main(String[] args) {

        IBird seagull = () -> {
            System.out.println("seagull is flying");

        };
        seagull.fly();

    }

}

*进入classes目录,我们可以发现仅有两个class文件:
IBird.class
LambdaTest.class*

使用javap指令反编译LambdaTest.class可以得到如下字节码

public class com.alios.d.LambdaTest {

 Constant pool:

   #1 = Methodref          #8.#25         // java/lang/Object."<init>":()V

   #2 = InvokeDynamic      #0:#30         // #0:fly:()Lcom/alios/d/IBird;

   #3 = InterfaceMethodref #31.#32        // com/alios/d/IBird.fly:()V

   #30 = NameAndType        #42:#43        // fly:()Lcom/alios/d/IBird;



  public static void main(java.lang.String[]);

    Code:

       0: invokedynamic #2,  0              // 动态调用指令

       5: astore_1
       
       6: aload_1
       
       7: invokeinterface #3,  1            // 调用接口方法IBird.fly
       
      12: return
}

从上文字节码可以看出Lambda采用的是invokedynamic指令,而非构建一个新的class。

invokedynamic指令

invokedynamic指令是JDK 1.7 JSR 292 引入的,当时的目的是为了支持Groovy、JRuby等动态类型语言,但是在JDK 1.8中,该指令又被用到了Lambda表达式实现中。

invokedynamic indexbyte1 indexbyte2 0 0

根据JVM 1.8规范,invokedynamic有4个操作数,前两个操作数构成一个索引[ (indexbyte1 << 8) | indexbyte2 ],指向类常量池,后两个操作数为保留字。
从上文字节码可以看出,invokedynamic的操作数指向了常量池的CONSTANT_InvokeDynamic_info结构

invokedynamic执行步骤如下:
* invokedynamic指令行,被称为动态调用点
* 当首次执行该invokedynamic调用时,JVM会调用一个bootstrap方法,并返回一个CallSite的对象,这个CallSite对象将永久与 此动态调用点关联
* 执行与CallSite关联的MethodHandle指向的方法

使用invokedynamic指令实现Lambda表达式,带来如下的好处: * 开销少,没有了匿名内部类的初始化过程 * 仅Lambda表达式首次调用的时候,进行转换和链接;之后的调用都会跳过这一步骤

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值