深入浅出JVM(六)之前端编译过程与语法糖原理

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 深入浅出JVM(六)之前端编译过程与语法糖原理

本篇文章将围绕Java中的编译器,深入浅出的解析前端编译的流程、泛型、条件编译、增强for循环、可变长参数、lambda表达式等语法糖原理

编译器与执行引擎

编译器

Java中的编译器不止一种,Java编译器可以分为:前端编译器、即时编译器和提前编译器

最为常见的就是前端编译器javac,它能够将Java源代码编译为字节码文件,它能够优化程序员使用起来很方便的语法糖

即时编译器是在运行时,将热点代码直接编译为本地机器码,而不需要解释执行,提升性能

提前编译器将程序提前编译成本地二进制代码

前端编译过程

  • 准备阶段: 初始化插入式注解处理器
  • 处理阶段
  • 解析与填充符号表
  1. 词法分析: 将Java源代码的字符流转变为token(标记)流
  • 字符: 程序编写的最小单位
  • 标记(token) : 编译的最小单位
  • 比如 关键字 static 是一个标记 / 6个字符
  1. 语法分析: 将token流构造成抽象语法树
  2. 填充符号表: 产生符号信息和符号地址
  • 符号表是一组符号信息和符号地址构成的数据结构
  • 比如: 目标代码生成阶段,对符号名分配地址时,要查看符号表上该符号名对应的符号地址
  • 插入式注解处理器的注解处理
  1. 注解处理器处理特殊注解: 在编译器允许注解处理器对源代码中特殊注解作处理,可以读写抽象语法树中任意元素,如果发生了写操作,就要重新解析填充符号表
  • 比如: Lombok通过特殊注解,生成get/set/构造器等方法
  • 语义分析与字节码生成
  1. 标注检查: 对语义静态信息的检查以及常量折叠优化
 int i = 1;
 char c1 = 'a';
 int i2 = 1 + 2;//编译成 int i2 = 3 常量折叠优化
 char c2 = i + c1; //编译错误 标注检查 检查语法静态信息 

image.png

  1. 数据及控制流分析: 对程序运行时动态检查
  • 比如方法中流程控制产生的各条路是否有合适的返回值
  1. 解语法糖: 将(方便程序员使用的简洁代码)语法糖转换为原始结构
  2. 字节码生成: 生成<init>,<clinit>方法,并根据上述信息生成字节码文件

前端编译流程图

image.png

源码分析

image.png

代码位置在JavaCompiler的compile方法中

image.png

Java中的语法糖

泛型

将操作的数据类型指定为方法签名中一种特殊参数,作用在方法、类、接口上时称为泛型方法、泛型类、泛型接口

Java中的泛型是类型擦除式泛型,泛型只在源代码中存在,在编译期擦除泛型,并在相应的地方加上强制转换代码

与具现化式泛型(不会擦除,运行时也存在泛型)对比

  • 优点: 只需要改动编译器,Java虚拟机和字节码指令不需要改变
  • 因为泛型是JDK5加入的,为了满足对以前版本代码的兼容采用类型擦除式泛型
  • 缺点: 性能较低,使用没那么方便
  • 为提供基本类型的泛型,只能自动拆装箱,在相应的地方还会加速强制转换代码,所以性能较低
  • 运行期间无法获取到泛型类型信息
  • 比如书写泛型的List转数组类型时,需要在方法的参数中指定泛型类型
 public static <T> T[] listToArray(List<T> list,Class<T> componentType){
         T[] instance = (T[]) Array.newInstance(componentType, list.size());
         return instance;
 }

增强for循环与可变长参数

image.png

增强for循环 -> 迭代器

可变长参数 -> 数组装载参数

泛型擦除后会在某些位置插入强制转换代码

自动拆装箱

自动装箱、拆箱的错误用法

         Integer a = 1;
         Integer b = 2;
         Integer c = 3;
         Integer d = 3;
         Integer e = 321;
         Integer f = 321;
         Long g = 3L;
         //true
         System.out.println(c == d);//范围小,在缓冲池中
         //false
         System.out.println(e == f);//范围大,不在缓冲池中,比较地址因此为false
         //true
         System.out.println(c == (a + b));
         //true
         System.out.println(c.equals(a + b));
         //false
         System.out.println(g == (b + a));
         //true
         System.out.println(g.equals(a + b));
  • 注意:
  1. 包装类重写的equals方法中不会自动转换类型

image.png

  1. 包装类的 == 就是去比较引用地址,不会自动拆箱

条件编译

布尔类型 + if语句 : 根据布尔值类型的真假,编译器会把分支中不成立的代码块消除(解语法糖)

image.png

Lambda原理

编写函数式接口

 @FunctionalInterface
 interface LambdaTest {
     void lambda();
 }

编写测试类

 public class Lambda {
     private int i = 10;
 ​
     public static void main(String[] args) {
         test(() -> System.out.println("匿名内部类实现函数式接口"));
     }
 ​
     public static void test(LambdaTest lambdaTest) {
         lambdaTest.lambda();
     }
 }

使用插件查看字节码文件

image.png

生成了一个私有静态的方法,这个方法中很明显就是lambda中的代码

在使用lambda表达式的类中隐式生成一个静态私有的方法,这个方法代码块就是lambda表达式中写的代码

image.png

执行class文件时带上参数java -Djdk.internal.lambda.dumpProxyClasses 包名.类名即可显示出这个匿名内部类

image.png

使用invokedynamic生成了一个实现函数式接口的匿名内部类对象,在重写函数式接口的方法实现中调用使用lambda表达式类中隐式生成的静态私有方法

总结

本篇文章以Java中编译器的分类为开篇,深入浅出的解析前端编译的流程,Java中泛型、增强for循环、可变长参数、自动拆装箱、条件编译以及Lambda等语法糖的原理

前端编译先将字符流转换为token流,再将token流转换为抽象语法树,填充符号表的符号信息、符号地址,然后注解处理器处理特殊注解(比如Lombok生成get、set方法),对语法树发生写改动则要重新解析、填充符号,接着检查语义静态信息以及常量折叠,对运行时程序进行动态检查,再解语法糖,生成init实例方法、clinit静态方法,最后生成字节码文件

Java中为了兼容之前的版本使用类型擦除式的泛型,在编译期间擦除泛型并在相应位置加上强制转换,想为基本类型使用泛型只能搭配自动拆装箱一起使用,性能有损耗且在运行时无法获取泛型类型

增加for循环则是使用迭代器实现,并在适当位置插入强制转换;可变长参数则是创建数组进行装载参数

自动拆装箱提供基本类型与包装类的转换,但包装类尽量不使用==,这是去比较引用地址,同类型比较使用equals

条件编译会在if-else语句中根据布尔类型将不成立的分支代码块消除

lambda原理则是通过invokeDynamic指令动态生成实现函数式接口的匿名对象,匿名对象重写函数时接口方法中调用使用lambda表达式类中隐式生成的静态私有的方法(该方法就是lambda表达式中的代码内容)


相关文章
|
1月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
1月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
41 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
1月前
|
编解码 前端开发 JavaScript
前端:Rem 及其转换原理
Rem是一种用于前端开发的相对字体大小单位,它基于根元素的字体尺寸来定义文本大小,有助于实现响应式布局和可维护性。Rem的转换原理是通过相对于HTML根元素的字体大小来设置子元素的字体大小,从而实现统一的比例调整,提高页面的适应性和灵活性。此方法简化了跨浏览器和设备的布局调整,增强了用户体验。
|
1月前
|
缓存 JavaScript 前端开发
拿下奇怪的前端报错(三):npm install卡住了一个钟- 从原理搞定安装的全链路问题
本文详细分析了 `npm install` 过程中可能出现的卡顿问题及解决方法,包括网络问题、Node.js 版本不兼容、缓存问题、权限问题、包冲突、过时的 npm 版本、系统资源不足和脚本问题等,并提供了相应的解决策略。同时,还介绍了开启全部日志、使用替代工具和使用 Docker 提供 Node 环境等其他处理方法。
428 0
|
1月前
|
存储 安全 前端开发
在前端开发中需要考虑的常见web安全问题和攻击原理以及防范措施
在前端开发中需要考虑的常见web安全问题和攻击原理以及防范措施
139 0
|
2月前
|
移动开发 前端开发 JavaScript
浅谈前端路由原理hash和history
该文章详细解析了前端路由的两种模式——Hash模式与History模式的工作原理及其实现方式,并通过实例代码展示了如何在实际项目中运用这两种路由模式。
|
2月前
|
移动开发 缓存 前端开发
构建高效的前端路由系统:从原理到实践
在现代Web开发中,前端路由系统已成为构建单页面应用(SPA)不可或缺的核心技术之一。不同于传统服务器渲染的多页面应用,SPA通过前端路由技术实现了页面的局部刷新与无缝导航,极大地提升了用户体验。本文将深入剖析前端路由的工作原理,包括Hash模式与History模式的实现差异,并通过实战演示如何在Vue.js框架中构建一个高效、可维护的前端路由系统。我们还将探讨如何优化路由加载性能,确保应用在不同网络环境下的流畅运行。本文不仅适合前端开发者深入了解前端路由的奥秘,也为后端转前端或初学者提供了从零到一的实战指南。
|
1月前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
3月前
|
开发者 安全 UED
JSF事件监听器:解锁动态界面的秘密武器,你真的知道如何驾驭它吗?
【8月更文挑战第31天】在构建动态用户界面时,事件监听器是实现组件间通信和响应用户操作的关键机制。JavaServer Faces (JSF) 提供了完整的事件模型,通过自定义事件监听器扩展组件行为。本文详细介绍如何在 JSF 应用中创建和使用事件监听器,提升应用的交互性和响应能力。
37 0
|
3月前
|
前端开发 JavaScript 中间件
【前端状态管理之道】React Context与Redux大对决:从原理到实践全面解析状态管理框架的选择与比较,帮你找到最适合的解决方案!
【8月更文挑战第31天】本文通过电子商务网站的具体案例,详细比较了React Context与Redux两种状态管理方案的优缺点。React Context作为轻量级API,适合小规模应用和少量状态共享,实现简单快捷。Redux则适用于大型复杂应用,具备严格的状态管理规则和丰富的社区支持,但配置较为繁琐。文章提供了两种方案的具体实现代码,并从适用场景、维护成本及社区支持三方面进行对比分析,帮助开发者根据项目需求选择最佳方案。
60 0