本篇文章将围绕Java中的编译器,深入浅出的解析前端编译的流程、泛型、条件编译、加强for循环、可变长参数、lambda表达式等语法糖原理
编译器与执行引擎
编译器
Java中的编译器不止一种,Java编译器能够分为:前端编译器、即时编译器和提前编译器
最为常见的就是前端编译器javac,它可能将Java源代码编译为字节码文件,它可能优化程序员应用起来很不便的语法糖
即时编译器是在运行时,将热点代码间接编译为本地机器码,而不须要解释执行,晋升性能
提前编译器将程序提前编译成本地二进制代码
前端编译过程
- 筹备阶段: 初始化插入式注解处理器
解决阶段
解析与填充符号表
词法剖析: 将Java源代码的字符流转变为token(标记)流
- 字符: 程序编写的最小单位
- 标记(token) : 编译的最小单位
- 比方 关键字 static 是一个标记 / 6个字符
- 语法分析: 将token流结构成形象语法树
填充符号表: 产生符号信息和符号地址
- 符号表是一组符号信息和符号地址形成的数据结构
- 比方: 指标代码生成阶段,对符号名调配地址时,要查看符号表上该符号名对应的符号地址
插入式注解处理器的注解解决
注解处理器解决非凡注解: 在编译器容许注解处理器对源代码中非凡注解作解决,能够读写形象语法树中任意元素,如果产生了写操作,就要从新解析填充符号表
- 比方: Lombok通过非凡注解,生成get/set/结构器等办法
语义剖析与字节码生成
标注查看: 对语义动态信息的查看以及常量折叠优化
int i = 1; char c1 = 'a'; int i2 = 1 + 2;//编译成 int i2 = 3 常量折叠优化 char c2 = i + c1; //编译谬误 标注查看 查看语法动态信息
数据及控制流剖析: 对程序运行时动静查看
- 比方办法中流程管制产生的各条路是否有适合的返回值
- 解语法糖: 将(不便程序员应用的简洁代码)语法糖转换为原始构造
- 字节码生成: 生成
<init>,<clinit>
办法,并根据上述信息生成字节码文件
前端编译流程图
源码剖析
代码地位在JavaCompiler的compile办法中
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循环与可变长参数
加强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));
留神:
- 包装类重写的equals办法中不会主动转换类型
- 包装类的 == 就是去比拟援用地址,不会主动拆箱
- 包装类重写的equals办法中不会主动转换类型
条件编译
布尔类型 + if语句 : 依据布尔值类型的虚实,编译器会把分支中不成立的代码块打消(解语法糖)
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(); } }
应用插件查看字节码文件
生成了一个公有动态的办法,这个办法中很显著就是lambda中的代码
在应用lambda表达式的类中隐式生成一个动态公有的办法,这个办法代码块就是lambda表达式中写的代码
执行class文件时带上参数java -Djdk.internal.lambda.dumpProxyClasses 包名.类名
即可显示出这个匿名外部类
应用invokedynamic
生成了一个实现函数式接口的匿名外部类对象,在重写函数式接口的办法实现中调用应用lambda表达式类中隐式生成的动态公有办法
总结
本篇文章以Java中编译器的分类为开篇,深入浅出的解析前端编译的流程,Java中泛型、加强for循环、可变长参数、主动拆装箱、条件编译以及Lambda等语法糖的原理
前端编译先将字符流转换为token流,再将token流转换为形象语法树,填充符号表的符号信息、符号地址,而后注解处理器解决非凡注解(比方Lombok生成get、set办法),对语法树产生写改变则要从新解析、填充符号,接着查看语义动态信息以及常量折叠,对运行时程序进行动静查看,再解语法糖,生成init实例办法、clinit静态方法,最初生成字节码文件
Java中为了兼容之前的版本应用类型擦除式的泛型,在编译期间擦除泛型并在相应地位加上强制转换,想为根本类型应用泛型只能搭配主动拆装箱一起应用,性能有损耗且在运行时无奈获取泛型类型
减少for循环则是应用迭代器实现,并在适当地位插入强制转换;可变长参数则是创立数组进行装载参数
主动拆装箱提供根本类型与包装类的转换,但包装类尽量不应用==,这是去比拟援用地址,同类型比拟应用equals
条件编译会在if-else语句中依据布尔类型将不成立的分支代码块打消
lambda原理则是通过invokeDynamic
指令动静生成实现函数式接口的匿名对象,匿名对象重写函数时接口办法中调用应用lambda表达式类中隐式生成的动态公有的办法(该办法就是lambda表达式中的代码内容)
最初(不要白嫖,一键三连求求拉\~)
本篇文章笔记以及案例被支出 gitee-StudyJava、 github-StudyJava 感兴趣的同学能够stat下继续关注喔\~
有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下\~
关注菜菜,分享更多干货,公众号:菜菜的后端私房菜
本文由博客一文多发平台 OpenWrite 公布!