编译器处理--语法糖
java中类加载的完整流程大概包括
编译器处理-->字节码文件-->类加载阶段-->运行期优化
第一部分 编译器处理 主要涉及到一些java语言中的语法糖,本文列出了大部分语法糖优化及其底层转换原理,同时这部分涉及到面试时两个常见考点
- 泛型擦除
- lambda表达式和匿名内部类的原理
语法糖
java编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担
生成默认无参构造方法
其实是调用父类的构造方法
自动拆/装箱
Integer.valueOf(1);//装箱
integer.intValue();//拆箱
泛型集合
泛型是在 JDK 5 开始加入的特性,但 java 在编译泛型代码后会执行 泛型擦除 的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理:
public class Candy3 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10); // 实际调用的是 List.add(Object e)
Integer x = list.get(0); // 实际调用的是 Object obj = List.get(int index);
}
}
所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作:
// 需要将 Object 转为 Integer
Integer x = (Integer)list.get(0);
如果前面的 x 变量类型修改为 int 基本类型那么最终生成的字节码是:
// 需要将 Object 转为 Integer,并执行拆箱操作
int x = ((Integer)list.get(0)).intValue();
foreach循环
- 对数组:底层是fori循环
- 对实现了Iterable接口的集合:迭代器循环
switch字符串
转为switch比较hash码+equals
switch枚举类
通过jvm层面生成一个内部类用一个int数组来存储枚举类的序号及其对应的整数值,数组大小即为枚举值的个数
switch通过枚举类序号和对应整数值作比较
枚举类
生成一个final修饰,继承Enum接口的class类
匿名内部类
会为接口创建一个新的类,类中重写目标方法,然后外部调用时等于new一个新的类对象,然后调用目标方法
注:
如果目标方法是有参数的,那么创建class类时会将参数作为成员变量,并通过有参构造来赋值
这也是为什么匿名内部类的方法参数需要final修饰,因为新创建class内的成员变量只有在初始化时能赋予值,然后则无法修改,因此需要外部变量被final修饰来确保不变性
lambda表达式与匿名内部类
-
lambda表达式通过编译时生成一个实现了函数式接口的匿名类
具体详细实现:
首次执行 Lambda 时:
- JVM 通过
invokedynamic
触发LambdaMetafactory
。 - 动态生成一个类实现目标接口,覆盖其唯一抽象方法。
- 该方法内部调用编译时生成的静态方法(即 Lambda 体)。
- 后续调用直接使用已生成的类,无需重复创建。
- JVM 通过
-
函数式接口
@FunctionalInterface 注解
- 确保唯一抽象方法:被
@FunctionalInterface
注解的接口必须有且仅有一个抽象方法(允许有默认方法、静态方法或覆盖Object
类的方法,如toString()
)。 - 防御性编程:防止接口被意外修改,破坏已有的 Lambda 实现,如果接口不满足条件(如没有抽象方法或多个抽象方法),编译器会报错。
- 确保唯一抽象方法:被
-
和匿名内部类的区别:
函数式接口
- 匿名内部类:可以继承类或实现任意接口,不限于函数式接口。
- Lambda 表达式:必须实现一个函数式接口,即只有一个抽象方法的接口。
编译时行为
- 匿名内部类:在编译时会创建一个完整的类文件,每个匿名内部类都是一个独立的类。
- Lambda 表达式:在编译时会被转换为一个实现了函数式接口的匿名类的实例,这个匿名类通常不会生成单独的类文件。
记录fengdongnan的知识产出文档,欢迎大家来一起交流学习