Javac编译器

Java语言的编译器是一段不确定的操作过程,可能是讲Java文件转变为class文件的过程,也可能是指虚拟机的后端编译,讲字节码转换为机器码的过程,还肯是静态提前编译器直接讲Java文件编译为本地机器代码的过程。

  • 前端编译器:Sun的JavacEclipse JDT中的增量式编译器ECJ
  • JIT编译器:HotSpotVM的C1C2编译器
  • AOT编译器:GNU Compiler for the Java(GCJ)Excelsior JET

编译过程详解

Javac编译器是由Java语言编写的程序,Javac的编译过程可以大致分为三个过程

  1. 解析与填充符号表过程
  2. 插入式注解的注解处理过程
  3. 分析与字节码生成过程

在这里插入图片描述

Javac编译动作的入口是com.sun.tools.Javac.main.JavaCompiler类,上述三个过程的代码逻辑集中在这个类的compile()compile2()方法里,整个编译最关键的处理就由图中标注的8个方法来完成,下面我们具体看一下这8个方法实现了什么功能。
在这里插入图片描述

解析与填充符号表过程

解析步骤由parseFiles()方法完成,解析步骤包括了词法分析语法分析

词法分析与语法分析

词法分析:将源代码的字符流转变为标记(Token)集合,标记是编译过程中的最小元素,如:关键字变量名字面量运算符都可以成为标记。int a = b + 2int、a、=、b、+、2都是标记。在Java中词法分析过程由com.sun.tools.javac.parser.Scanner类来实现。注意不是java.lang.Scanner
语法分析:根据Token序列来构造抽象语法树的过程,抽象语法树:用来描述程序语言语法结构的树形表示方式。语法树的每一个节点都代表者程序代码中的一个语法结构。如:类型修饰符运算符接口返回值甚至连代码注释等都可以是一个语法结构。
经过词法,语法分析后编译器基本就不会对源码文件进行操作了。后续的操作都建立在抽象语法树上。

填充符号表

将一组符号地址和符号信息构成表格,可以理解为K-V,也可以是有序符号表,树状符号表,在语义分析中,符号表所登记的内容将用于语义检查和产生中间代码,在目标代码生成阶段当对符号名进行地址分配时,可以通过符号表找到其地址。
在Javac源码中,填充符号表的过程由com.sun.tools.comp.Enter类实现。如果下载了OpenJDK源码的话具体目录为src\jdk.compiler\share\classes\com\sun\tools\javac\comp

注解处理器

我们使用的注解标准API其实可以算是一个编译器插件,有了编译器注解处理的标准API后,我们的代码才有可能干涉编译器的行为。

处理注解的流程

  1. 注解处理器的发现与初始化
    • 编译器在编译过程中会通过META-INF/services/javax.annotation.processing.Processor文件找到所有可用的注解处理器。
    • 调用每个处理器的init方法进行初始化。
  2. 注解处理轮次
    • 编译器在多个轮次中调用注解处理器的process方法,每个轮次提供一组被注解标注的元素。
    • 每个轮次中,处理器可以生成新的源代码文件,这些文件会在下一个轮次中被编译并再次处理。
    • 处理结束时,RoundEnvironment.processingOver()返回true,表示所有注解处理完成。
  3. 注解处理逻辑
    • 在process方法中,注解处理器会根据注解类型获取相应的元素(类、方法、字段等)。
    • 处理器可以读取注解的值,执行逻辑,并使用Filer生成新的源代码、配置文件等。
  4. 生成代码和编译消息
    • 使用Filer生成文件:
Filer filer = processingEnv.getFiler();
JavaFileObject fileObject = filer.createSourceFile("com.example.GeneratedClass");
Writer writer = fileObject.openWriter();
writer.write(generatedCode);
writer.close();

语义分析与字节码生成

语义分析

语义分析主要是检查代码逻辑和语法是否是符合程序规范

如下:

int a = 1;
boolean b = false;
char c = 2;

后续可能出现的赋值运算如下

int d = a + c;
int e = b + c;
char f = a + c;

因为a为int,b为boolean,c为char,所以a+c是正确的,但是b+c和a+c是错误的,因为boolean无法参与运算,a+c后类型进行了升级必须强转为char。所以检查这些语法是否错误就是语法分析干的事情。
语义分析又可以分标注检查数据及控制流分析两个步骤

1.标注检查

标记检查步骤:

  • 变量使用前是否已被声明
  • 变量与赋值之间的数据类型是否能够匹配
  • 常量折叠–>a = 1 + 2可以被折叠为a = 3
    • 所以在程序运行时期a = 1 + 2并不会影响程序的效率和a=3是一样的
    • 但是所谓常量之间的运算如10241024或者606024我们都可以通过阅读代码时能大概了解代码的含义,如单位为byte时10241024可以理解为MB,单位为秒时可606024可以理解为一天。
2.数据及控制流分析

是对程序上下文逻辑的进近一步验证,它可以检查出注入程序局部变量在使用前是否有赋值,方法的每条路径是否都有返回值,是否所有的检查异常都被正确处理等问题

解语法糖

Javac中解语法糖的过程由desugar()方法触发,在com.sun.tools.javac.comp.TransTypes类和com.sun.tools.javac.comp.Lower类中完成。

字节码生成

此过程是javac编译过程的最后一个阶段com.sun.tools.javac.jvm.Gen完成。主要是将前面各个步骤所生成的信息转换为字节码写入磁盘中,编译器还进行少量代码添加和转换工作。如实例构造器和类构造器<clinit>()方法就是这个阶段添加到语法树中的。
完成了对语法树的遍历和调整后,就会把填充了所有所需信息的符号表交到com.sun.tools.javac.jvm.ClassWriter类手中,由这个类的writeClass()方法输出字节码,生成最终的Class文件

Javac解析语法糖

Java的语法糖有许多,但是这里挑重要的我们用的最多的举例

泛型与类型擦除

泛型本质是参数化类型的应用,将类型作为参数传递。最早出现在C++语言中。和C#不同的是,java语言的泛型规则是:只在源码中存在,在编译后的字节码文件中就已经被替换为原来的原生类型了。并且在相应的地方插入了强制类型转换的代码。

Java的伪泛型

根据上面的描述Java中的泛型更像是一种伪泛型,被称为类型擦除。

public static void main(String[] args) {
    Map<String, String> map = new HashMap<>();
    map.put("a", "a");
    map.put("b", "b");
    System.out.println(map.get("a"));
    System.out.println(map.get("b"));
}

把这段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了Jva泛型出现之前的写法,泛型类型都变回了原生类型,如下

public static void main(String[] args) {
    Map map = new HashMap();
    map.put("a", "a");
    map.put("b", "b");
    System.out.println((String) map.get("a"));
    System.out.println((String) map.get("b"));
}

伪泛型的特殊

如下代码

public class Test {

    public static void method(List<String> list) {
        System.out.println("invoke method(List<String>list)");
    }

    public static void method(List<Integer> list) {
        System.out.println("invoke method(List<Integer>list)");

    }

}

因为编译后的类型擦除,所以导致不能编译,最直观的就是代码编辑器会提示如下
在这里插入图片描述

不仅是代码编辑器还有对于编译器来说,你两个方法进行了类型擦擦除后是一模一样的。但是当使用Sun JDK的java从编译器编译以下代码,却有运行结果。

public class Test {

    public static void main(String[] args) {
        method(new ArrayList<String>());
        method(new ArrayList<Integer>());
    }
    
    public static void method(List<String> list) {
        System.out.println("invoke method(List<String>list)");
    }

    public static void method(List<Integer> list) {
        System.out.println("invoke method(List<Integer>list)");

    }

}

结果

invoke method(List<String>list)
invoke method(List<Integer>list)

通过这里可以看出类型擦除并不是导致无法重载的全部原因。这是因为虽然返回值并不是方法的特征签名,但是在Class文件格式中,只要描述符不是完全一致的两个方法就可以共同存在
但是在**49.0**版本之后的虚拟机能够识别Signature参数,来解决在字节码层面给方法存储特征签名,保存了参数化类型的信息。

自动拆装箱和遍历循环

看下面这个例子就可以知道自动拆装箱、增强for、泛型、变长参数的本质

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4);
    int sum = 0;
    for (int i : list) {
        sum += i;
    }
    System.out.println(sum);
}

语法糖解析后如下

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(new Integer[] {
        Integer.valueOf(1),
        Integer.valueOf(2),
        Integer.valueOf(3),
        Integer.valueOf(4),
    });
    int sum = 0;
    for (Iterator iterator = list.iterator() ; iterator.hasNext();) {
        int i = ((Integer) iterator.next()).intValue();
        sum += i;
    }
    System.out.println(sum);
}

条件编译

Java的if在编译期间就会被执行,如下

public static void main(String[] args) {
    if (true) {
        System.out.println(1);
    } else {
        System.out.println(2);
    } 
}

会被编译成

public static void main(String[] args) {
    System.out.println(1);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/744991.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DevExpress WPF中文教程:Grid - 如何排序、分组、过滤数据(设计时)?

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

php反序列化漏洞简介

目录 php序列化和反序列化简介 序列化 反序列化 类中定义的属性 序列化实例 反序列化实例 反序列化漏洞 序列化返回的字符串格式 魔术方法和反序列化利用 绕过wakeup 靶场实战 修复方法 php序列化和反序列化简介 序列化 将对象状态转换为可保持或可传输的格式的…

构建家庭NAS之三:在TrueNAS SCALE上安装qBittorrent

本系列文章索引&#xff1a; 构建家庭NAS之一&#xff1a;用途和软硬件选型 构建家庭NAS之二&#xff1a;TrueNAS Scale规划、安装与配置 构建家庭NAS之三&#xff1a;在TrueNAS SCALE上安装qBittorrent 大部分家庭NAS用户应该都会装一个下载工具。本篇以qBittorrent为例&…

如何使得Macos的剪切板感知fileURL并当fileURL被执行paste 动作时 回调到某个监听的函数 从而来填充file content

问题及尝试&#xff1a; 我在做一个跨平台文件拷贝的功能&#xff0c;文件可能是从其他操作系统比如Linux 或者Windows 拷贝到Macos上&#xff0c; 但是我试过所有可以hook NSPasteboard的方法&#xff0c;确实没有找到可以监听macos 剪切板的方法&#xff0c;因为fileURL 确实…

基于STM32设计的智能家居远程调温系统(通过红外线控制空调)_75

文章目录 一、前言1.1 项目介绍【1】项目功能介绍【2】项目硬件模块组成1.2 设计思路【1】整体设计思路【2】ESP8266工作模式配置1.3 设计的意义1.4 开发工具的选择1.5 系统框架图1.6 系统功能总结1.7 原理图二、硬件选型2.1 ESP8266-串口WIFI2.2 STM32F103C8T6开发板2.3 红外学…

YoloV7改进策略:SPD-Conv加入到YoloV7中,让小目标无处遁形

摘要 SPD-Conv是一种新的构建块&#xff0c;用于替代现有的CNN体系结构中的步长卷积和池化层。它由一个空间到深度&#xff08;SPD&#xff09;层和一个非步长卷积&#xff08;Conv&#xff09;层组成。 空间到深度&#xff08;SPD&#xff09;层的作用是将输入特征图的每个空…

文华WH7主图多空预警系统指标公式源码

RSV:(CLOSE-LLV(LOW,9))/(HHV(HIGH,9)-LLV(LOW,9))*100;//收盘价与N周期最低值做差&#xff0c;N周期最高值与N周期最低值做差&#xff0c;两差之间做比值定义为RSV K:SMA(RSV,3,1);//RSV的移动平均 D:SMA(K,3,1);//K值的移动平均 DIFF : EMA(CLOSE,12) - EMA(CLOSE,26); D…

1uH电感SK6615电流1.5A频率2MHz输入5.5V同步降压转换器

SK6615C 1.5A 2MHz 5.5V同步降压转换器 SK6615 SOT23-5封装和丝印LA 描述 该SK6615C是一款高效、DC-DC降压型开关稳压器&#xff0c;能够提供高达1.5A的输出电流。该器件的工作输入电压范围为 2.6V 至 5.5V&#xff0c;输出电压范围为 0.6V 至 VIN。工作频率为2MHz&#xff0c…

神经网路学习7-线性模型

一个最简单的线性模型&#xff0c;w是权重&#xff0c;一般来说会取随机值&#xff0c;然后不断学习直到与预期相同 如此以此取每个值与真实值的差值&#xff0c;即评估误差 即找一个合适的权重w&#xff0c;使得平均误差最小 上面的是针对单个样本的&#xff0c;后面的是对…

30-33、SpringBoot项目部署\属性配置方式\多环境开发(一个文件)\多环境分组(多个文件)

1、打包插件:和springboot的版本保持一致 根pom <build><plugins><!--打包插件--><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>3.1.3</versi…

urfread刷算法题day7|118. 杨辉三角

观察可得&#xff1a;每行第一个和最后一个&#xff0c;都是1. 而且每行的元素个数也是只比上一行多1个 中间的元素计算的时候&#xff0c;值为它上一行相邻两个元素的和。 考验ArrayList基本功 class Solution {public List<List<Integer>> generate(int numRow…

并发编程理论基础——合适的线程数量和安全的局部变量(十)

多线程的提升方向 主要方向在于优化算法和将硬件的性能发挥到极致想要发挥出更多的硬件性能&#xff0c;最主要的就是提升I/O的利用率和CPU的利用率以及综合利用率操作系统已经解决了磁盘和网卡的利用率问题&#xff0c;利用中断机制还能避免 CPU 轮询 I/O 状态&#xff0c;也提…

【算能全国产AI盒子】基于BM1688CV186AH+FPGA智能物联工作站,支持差异化泛AI视觉产品定制

在数据呈现指数级增长的今天&#xff0c;越来越多的领域和细分场景对实时、高效的数据处理和分析的需求日益增长&#xff0c;对智能算力的需求也不断增强。为应对新的市场趋势&#xff0c;凭借自身的硬件研发优势&#xff0c;携手算能相继推出了基于BM1684的边缘计算盒子&#…

VS Code 配置cmake(Linux环境)

通过sudo apt install cmake在linux上安装cmake 在Vs Code中安装这两个插件 通过命令whereis cmake获取linux中cmake的路径信息 右键CMake Tools右下角齿轮标志&#xff0c;选择扩展设置&#xff08;Extension Settings&#xff09; 注意要设置的是本地&#xff0c;还是远程连接…

如何在FastAPI服务器中添加黑名单和白名单实现IP访问控制

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 添加黑名单功能步骤1:安装依赖步骤2:创建FastAPI应用步骤3:添加黑名单📝 添加白名单功能步骤1:创建白名单列表步骤2:添加白名单检查⚓️ 相关链接 ⚓️📖 介绍 📖 在现代网络应用开发中,为了增强…

(9)农作物喷雾器

文章目录 前言 1 必要的硬件 2 启用喷雾器 3 配置水泵 4 参数说明 前言 Copter 包括对农作物喷雾器的支持。该功能允许自动驾驶仪连接到一个 PWM 操作的泵和&#xff08;可选&#xff09;旋转器&#xff0c;根据飞行器速度控制液体肥料的流动速度。 稍微过时的视频显示了…

【PB案例学习笔记】-24创建一个窗口图形菜单

写在前面 这是PB案例学习笔记系列文章的第24篇&#xff0c;该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上传到了gite…

第一百二十九节 Java面向对象设计 - Java枚举比较

Java面向对象设计 - Java枚举比较 您可以通过三种方式比较两个枚举常量&#xff1a; 使用Enum类的compareTo()方法使用Enum类的equals()方法使用运算符 Enum类的compareTo()方法比较同一枚举类型的两个枚举常量。它返回两个枚举常量的序数差。如果两个枚举常量相同&#xff0…

《山西化工》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答 问&#xff1a;《山西化工》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第一批认定学术期刊。 问&#xff1a;《山西化工》级别&#xff1f; 答&#xff1a;省级。主办单位&#xff1a;山西省工业和信息化厅 主管单位&#xff1a;山…

基于SaaS平台的iHRM管理系统测试学习

目录 目录 1、登录模块 2、员工管理模块 3、Postmannewman软件的安装&#xff0c;学习 1、Postman的使用 2、Postman断言 3、全局变量和环境变量 4、请求时间戳 5、Postman关联 6、批量执行测试用例 7、Postman生成测试报告 8、Postman读取外部数据文件&#xff08…