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

编译器与执行引擎

编译器

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

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

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

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

前端编译过程

  • 筹备阶段: 初始化插入式注解处理器
  • 解决阶段

    • 解析与填充符号表

      1. 词法剖析: 将Java源代码的字符流转变为token(标记)流

        • 字符: 程序编写的最小单位
        • 标记(token) : 编译的最小单位
        • 比方 关键字 static 是一个标记 / 6个字符
      2. 语法分析: 将token流结构成形象语法树
      3. 填充符号表: 产生符号信息和符号地址

        • 符号表是一组符号信息和符号地址形成的数据结构
        • 比方: 指标代码生成阶段,对符号名调配地址时,要查看符号表上该符号名对应的符号地址
    • 插入式注解处理器的注解解决

      1. 注解处理器解决非凡注解: 在编译器容许注解处理器对源代码中非凡注解作解决,能够读写形象语法树中任意元素,如果产生了写操作,就要从新解析填充符号表

        • 比方: Lombok通过非凡注解,生成get/set/结构器等办法
    • 语义剖析与字节码生成

      1. 标注查看: 对语义动态信息的查看以及常量折叠优化

         int i = 1; char c1 = 'a'; int i2 = 1 + 2;//编译成 int i2 = 3 常量折叠优化 char c2 = i + c1; //编译谬误 标注查看 查看语法动态信息 

      2. 数据及控制流剖析: 对程序运行时动静查看

        • 比方办法中流程管制产生的各条路是否有适合的返回值
      3. 解语法糖: 将(不便程序员应用的简洁代码)语法糖转换为原始构造
      4. 字节码生成: 生成<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));
  • 留神:

    1. 包装类重写的equals办法中不会主动转换类型
    2. 包装类的 == 就是去比拟援用地址,不会主动拆箱

条件编译

布尔类型 + 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 公布!