一、 匿名内部类
匿名内部类其实就是没有名字的内部类,但是其必须要实现一个接口或者继承一个父类,通常是用来简化代码
例程如下:
先定义一个 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指向的方法