Javac编译器

news2024/11/19 19:43:03

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.coloradmin.cn/o/1863610.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML5五十六个民族网站模板源码

文章目录 1.设计来源高山族1.1 登录界面演示1.2 注册界面演示1.3 首页界面演示1.4 中国民族界面演示1.5 关于高山族界面演示1.6 联系我们界面演示 2.效果和源码2.1 动态效果2.2 源代码2.3 源码目录 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.ne…

压箱底的15套无代码可视化数据大屏

学习视频smardaten致力于大数据、人工智能领域前沿技术研发&#xff0c;充分挖掘生产生活的海量数据&#xff0c;提供覆盖全行业的、超大规模的数据治理与智能分析服务&#xff0c;平台旨在用全方位、前沿化的大数据、人工智能等数据智能技术&#xff0c;突破行业数据管理难题&…

第1章 物联网模式简介---独特要求和体系结构原则

物联网用例的独特要求 物联网用例往往在功耗、带宽、分析等方面具有非常独特的要求。此外&#xff0c;物联网实施的固有复杂性&#xff08;一端的现场设备在计算上受到挑战&#xff0c;另一端的云容量几乎无限&#xff09;迫使架构师做出艰难的架构决策和实施选择。可用实现技…

分享一套基于SSM的美食推荐管理系统(源码+文档+部署)

大家好&#xff0c;今天给大家分享一套基于SSM的美食推荐管理系统 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringSpringMvcMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 博主介绍&#xff1a; 一名Java全栈工程师&#xff0c;专注于Jav…

高质量数据不够用,合成数据是打开 AGI 大门的金钥匙吗?

编者按&#xff1a; 人工智能技术的发展离不开高质量数据的支持。然而&#xff0c;现有可用的高质量数据资源已日渐接近枯竭边缘。如何解决训练数据短缺的问题&#xff0c;是当前人工智能领域亟待解决的一个较为棘手的问题。 本期文章探讨了一种经实践可行的解决方案 —— 合成…

EE trade:利弗莫尔三步建仓法

在股市投资领域&#xff0c;利弗莫尔这个名字代表着无数的智慧和经历。他的三步建仓法成为了投资者们趋之若鹜的学习对象。本文将详细解析利弗莫尔的著名买入法&#xff0c;通过分步进攻方式&#xff0c;有效掌控市场并实现盈利。 一、利弗莫尔的三步建仓法详解 利弗莫尔三步…

(9)农作物喷雾器

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

MYSQL 将某个字段赋值当前时间

如 我们需要将use_time 赋值为当前时间&#xff1a; 准备三条数据 &#xff1a; 执行sql &#xff0c;2种当前时间赋值函数&#xff0c;1种关键字赋值 &#xff1a; update test_info SET use_timeNOW() WHERE id 1; update test_info SET use_timeCURRENT_TIMESTAMP() …

Flink 反压

反压 Flink反压是一个在实时计算应用中常见的问题&#xff0c;特别是在流式计算场景中。以下是对Flink反压的详细解释&#xff1a; 一、反压释义 反压&#xff08;backpressure&#xff09;意味着数据管道中某个节点成为瓶颈&#xff0c;其处理速率跟不上上游发送数据的速率…

基于昇腾AI | Yolov7模型迁移到昇腾平台EA500I边缘计算盒子的实操指南

近年来&#xff0c;国产化替代的进程正在加快。在众多国产平台中&#xff0c;昇腾平台具有高性能、低功耗、易扩展、软件栈全面成熟等优势&#xff0c;其产品和技术在国内众多领域实现了广泛应用&#xff1b;作为昇腾的APN伙伴和IHV合作伙伴&#xff0c;英码科技携手昇腾推出了…

数据安全如何防护?迅软加密软件保护企业数据资产

前言&#xff1a;加密软件是一种重要的工具&#xff0c;可以帮助企业保护其数据资产的安全。通过使用加密算法&#xff0c;加密软件可以将敏感数据转化为无法理解的密文&#xff0c;只有授权的用户才能解密并访问这些数据。 一、迅软加密软件保护企业数据资产的关键方面 1、数…

小迪安全v2023笔记 1-18

小迪安全v2023笔记 1-18 棱角社区 文章目录 1. 基础入门1. 正向shell与反向shell2. web应用3. 抓包&#xff0c;封包&#xff0c;协议&#xff0c;app&#xff0c;小程序&#xff0c;pc应用&#xff0c;web应用 2. 信息打点1. 常见信息获取2. 文件泄露3. 常见阻碍4. CDN绕过&a…

硕思LOGO设计师软件下载附加详细安装教程

​根据行业数据显示硕思logo设计者中有图片渐变、文本效果、阴影、发光、斜角、倒影等多种多样的logo图形样子和款式&#xff0c;能够导出或打印logo来满足不同的使用要求&#xff0c;所见即所得的软件界面&#xff0c;在预览窗口中实时查看logo图形&#xff0c;对于设计和制作…

深度测试中的隐藏面消除技术

by STANCH 标签&#xff1a;#计算机图形学 #深度测试 #深度测试 #隐藏面消除 1.概述 根据我们的日常经验&#xff0c;近处的物体会挡住后面的物体&#xff0c;在三维场景中通常通过深度缓冲来实现这样的效果。深度缓冲记录着屏幕对应的每个像素的深度值。模型一开始所在的局部…

Linux的shell语法

Linux的shell脚本 1.概述 shell解释器&#xff0c;介于操作系统内核与用户之间&#xff0c;充当了一个“命令解释器”的角色&#xff0c;负责接收用户输入的操作指令&#xff08;命令&#xff09;并进行解释&#xff0c;将需要执行的操作传递给内核执行&#xff0c;并输出执行…

从零开始做题:老照片中的密码

老照片中的密码 1.题目 1.1 给出图片如下 1.2 给出如下提示 这张老照片中的人使用的是莫尔斯电报机&#xff0c;莫尔斯电报机分为莫尔斯人工电报机和莫尔斯自动电报机&#xff08;简称莫尔斯快机&#xff09;。莫尔斯人工电报机是一种最简单的电报机&#xff0c;由三个部分组…

计算机毕业设计Thinkphp/Laravel学生考勤管理系统zyoqy

管理员登录学生考勤管理系统后&#xff0c;可以对首页、个人中心、公告信息管理、年级管理、专业管理、班级管理、学生管理、教师管理、课程信息管理、学生选课管理、课程签到管理、请假申请管理、销假申请管理等功能进行相应操作&#xff0c;如图5-2所示。学生登录进入学生考勤…

玄子Share-本地部署 AI 大模型与构建知识库

玄子Share-本地部署 AI 大模型与构建知识库 部署环境概述 警告&#xff01;OpenAI 宣布全面封锁中国 API 接入 昨天&#xff0c;许多开发者从 OpenAI 那收到了一份警告信 您好&#xff0c; 据我们的数据监测&#xff0c;贵组织正从 OpenAl 当前未支持的区域产生 API 访问流量…

python基础篇(6):global关键字

使用 global关键字 可以在函数内部声明变量为全局变量 未使用global关键字的代码&#xff1a; # global关键字&#xff0c;在函数内声明变量为全局变量 num 200def test_a():print(f"test_a: {num}")def test_b():num 500print(f"test_b: {num}")test_…

护眼大路灯十大品牌有必要买很贵的吗?护眼落地灯十大品牌分享

护眼大路灯十大品牌有必要买很贵的吗&#xff1f;由于学习需要&#xff0c;很多小孩回家以后都会挑灯夜读&#xff0c;而夜间读写时的普通照明灯不够亮看书写字有阴影&#xff0c;再加上蓝光频闪严重&#xff0c;长期处于这种低质照明环境会加剧孩子用眼疲劳&#xff0c;睫状肌…