# 背景
最近面试中与几个候选人探讨过类似的问题,发现多数人对这一概念仍然比较模糊,本文将从字节码的角度简单介绍一下两者的区别
# 匿名内部类
匿名内部类其实就是没有名字的内部类,但是其必须要实现一个接口或者继承一个父类,通常是用来简化代码
例程如下:
先定义一个IAnimal
```java
public interface IAnimal {
void run();
void walk();
}
```
然后定义测试类NestedClassTest
```java
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: return
}
```
从上文的字节码可以看出,匿名内部类的调用方法与普通类没有区别
* 编译器会为每个匿名类生成一个新的class文件,文件名格式为className$num。
如果Lambda表达式也采用类似方式,将是极为不利的。
* 类加载需要有加载、验证、准备、解析、初始化等过程,大量的内部类将会影响应用执行的性能,并消耗Metaspace
# Lambda表达式
接下来,我们看一下Lambda表达式
先上代码,我们先定义一个函数式接口
```java
@FunctionalInterface
public interface IBird {
void fly();
}
```
然后定义一个Lambda表达式的测试类
```java
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表达式首次调用的时候,进行转换和链接;之后的调用都会跳过这一步骤
# 参考
JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform
https://jcp.org/en/jsr/detail?id=292
JSR 335: Lambda Expressions for the JavaTM Programming Language
https://www.jcp.org/en/jsr/detail?id=335
最近面试中与几个候选人探讨过类似的问题,发现多数人对这一概念仍然比较模糊,本文将从字节码的角度简单介绍一下两者的区别
# 匿名内部类
匿名内部类其实就是没有名字的内部类,但是其必须要实现一个接口或者继承一个父类,通常是用来简化代码
例程如下:
先定义一个IAnimal
```java
public interface IAnimal {
void run();
void walk();
}
```
然后定义测试类NestedClassTest
```java
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: return
}
```
从上文的字节码可以看出,匿名内部类的调用方法与普通类没有区别
* 编译器会为每个匿名类生成一个新的class文件,文件名格式为className$num。
如果Lambda表达式也采用类似方式,将是极为不利的。
* 类加载需要有加载、验证、准备、解析、初始化等过程,大量的内部类将会影响应用执行的性能,并消耗Metaspace
# Lambda表达式
接下来,我们看一下Lambda表达式
先上代码,我们先定义一个函数式接口
```java
@FunctionalInterface
public interface IBird {
void fly();
}
```
然后定义一个Lambda表达式的测试类
```java
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表达式首次调用的时候,进行转换和链接;之后的调用都会跳过这一步骤
# 参考
JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform
https://jcp.org/en/jsr/detail?id=292
JSR 335: Lambda Expressions for the JavaTM Programming Language
https://www.jcp.org/en/jsr/detail?id=335