以下是第十章 前端编译与优化的内容
Java中的编译可能是指:
- 前端编译:把*.java文件转变成*.class文件的过程
- 即时编译(JIT):运行期把字节码转变成本地机器码的过程
- 提前编译(AOT):直接把程序编译成与目标机器指令集相关的二进制代码的过程
这一章讲的是前端编译
Javac编译器
javac是一个由java语言编写的程序
Javac的源码与调试
Java6以后,Javac的源码放在了JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/javac
Javac的工作流程
- 准备过程:初始化插入式注解处理器。
- 解析与填充符号表过程,包括:
·词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树。
·填充符号表。产生符号地址和符号信息。 - 插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段。
- 分析与字节码生成过程,包括:
- 标注检查。对语法的静态信息进行检查。
- 数据流及控制流分析。对程序动态运行过程进行检查。
- 解语法糖。将简化代码编写的语法糖还原为原有的形式。
- 字节码生成。将前面各个步骤所生成的信息转化成字节码。
执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转回到之前的解析、填充符号表的过程中重新处理这些新符号。
Java语法糖的味道
泛型
Java与C#的泛型
Java是 “类型擦除式泛型”,C#是 “具现化式泛型”。
Java语言中的泛型只在程序源码中存在,在编译后的字节码文件中,全部泛型都被替换为原来的裸类型,并且在相应的地方插入了强制转型代码。
Java的类型擦除式泛型无论在使用效果上还是运行效率上,几乎是全面落后于C#的具现化式泛型,而它的唯一优势是在于实现这种泛型的影响范围上。
Java选择类型擦除主要是因为遗留代码多,向前兼容。
类型擦除
实现:泛型类原地泛型化变成裸类型
Java是简单粗暴地直接在编译时把ArrayList<Integer>
还原回ArrayList
,只在元素访问、修改时自动插入一些强制类型转换和检查指令。
类型擦除的缺陷:
- 不支持基本类型,例如
ArrayList<int>
,因为无法实现int和Object互转,只能自动装箱、拆箱使用Integer,导致执行效率低。 - 运行期无法取到泛型类型信息,导致不能出现下列操作:
if (item instanceof E) { // 不合法,无法对泛型进行实例判断
E newItem = new E(); // 不合法,无法使用泛型创建对象
E[] itemArray = new E[10]; // 不合法,无法使用泛型创建数组
- 方法重载即使是不同泛型类型的参数也不能作为不一样的特征签名。但是返回值类型不同实际是可以正常运行的,虽然特征签名还是一致,但class文件支持不同描述符的方法共存,后续JVM有优化。
值类型和未来的泛型
在2014年,刚好是Java泛型出现的十年之后,Oracle建立了一个名为Valhalla的语言改进项目,希望改进Java语言留下的各种缺陷(解决泛型的缺陷就是项目主要目标其中之一)。
在Valhalla项目中规划了几种不同的新泛型实现方案,在这些新的泛型设计中,泛型类型有可能被具现化,也有可能继续维持类型擦除以保持兼容(取决于采用哪种实现方案),即使是继续采用类型擦除的方案,泛型的参数化类型也可以选择不被完全地擦除掉,而是相对完整地记录在Class文件中,能够在运行期被使用,也可以指定编译器默认要擦除哪些类型。相对于使用不同方式实现泛型,目前比较明确的是未来的Java应该会提供“值类型”(Value Type)的语言层面的支持。
值类型可以与引用类型一样,具有构造函数、方法或是属性字段,等等,而它与引用类型的区别在于它在赋值的时候通常是整体复制,而不是像引用类型那样传递引用的。更为关键的是,值类型的实例很容易实现分配在方法的调用栈上的,这意味着值类型会随着当前方法的退出而自动释放,不会给垃圾收集子系统带来任何压力。
在Valhalla项目中,Java的值类型方案被称为“内联类型”,计划通过一个新的关键字inline来定义。
条件编译
使用条件为常量的if语句,可以在编译期间过滤掉一些代码
public static void main(String[] args) {
if (true) {
System.out.println("block 1");
} else {
System.out.println("block 2");
}
}
反编译的结果
public static void main(String[] args) {
System.out.println("block 1");
}
可变参数
public void foo(String... args){}
=> public void foo(String[] args){}
switch-字符串和switch-enum
switch-字符串
配合字符串的哈希值使用。
String i = "hello";
switch (i){
case "hello":
System.out.println("h");
break;
case "world":
System.out.println("w");
break;
}
反编译后
String i = "hello";
byte var3 = -1;
switch(i.hashCode()) {
case 99162322:
if (i.equals("hello")) {
var3 = 0;
}
break;
case 113318802:
if (i.equals("world")) {
var3 = 1;
}
}
switch(var3) {
case 0:
System.out.println("h");
break;
case 1:
System.out.println("w");
}
switch-enum
enum Sex{
MALE,FEMALE
}
public static void foo(Sex sex){
switch (sex){
case MALE:
System.out.println("男");
break;
case FEMALE:
System.out.println("女");
break;
}
}
反编译后
/*
*定义一个合成类(仅jvm使用,对我们不可见)
*用来映射枚举的ordina1与数组元素的关系
*枚举的ordinal表示枚举对象的序号,从0开始
*即MALE的ordinal()=0,FEMALE的ordinal()=1
*/
static class MAP{ //数组大小即为枚举元素个数,里面存储case用来对比的数字 static int[] map = new int[2]; static{ map[Sex.MALE.ordinal()]=1; map[Sex.FEMALE.ordinal()]=2; }
}
public static void foo(Sex sex)
{ int x =MAP.map[sex.ordinal()];
switch(x){
case 1:
System.out.println("男");
break;
case 2:
System.out.println("女");
break;
}
}
枚举
enum Sex{
MALE,FEMALE
}
实际上是编译成了
public final class Sex extends Enum<Sex>{
public static final Sex MALE;
public static final Sex FEMALE;
private static final Sex[] VALUES;
static{ MALE = new Sex("MALE",0); FEMALE = new Sex("FEMALE",1);VALUES = new Sex[]{MALE,FEMALE};
}
private Sex(String name,int ordinal){
super(name,ordinal);
}
public static Sex[] values(){
return $VALUES.clone();
}
public static Sex valueOf(String name){
return Enum.valueOf(Sex.class,name);
}
}
try-with-resources
JDK7开始新增了对需要关闭的资源处理的特殊语法try-with-resources
:
try(资源变量=创建资源对象){
}catch(){
}
其中资源对象需要实现Autocloseable
接口,例如InputStream、OutputStream、Connection、Statement、Resultset
等接口都实现了Autocloseable
,使用try-with-resources
可以不用写finally语句块,编译器会帮助生成关闭资源代码,例如:
public static void main(String[]args){
try(InputStream is = new FileInputStream("d:\\1.txt")){
System.out.println(is);
}catch(IOException e){
e.printStackTrace();
}
}
反编译后:
try {
InputStream is = new FileInputStream("d:\\1.txt");
Throwable var2 = null;
try {
System.out.println(is);
} catch (Throwable var12) {
var2 = var12;
throw var12;
} finally {
if (is != null) {
if (var2 != null) {
try {
is.close();
} catch (Throwable var11) {
var2.addSuppressed(var11);
}
} else {
is.close();
}
}
}
} catch (IOException var14) {
var14.printStackTrace();
}
原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3java%e8%99%9a%e6%8b%9f%e6%9c%ba%e7%ac%ac%e4%b8%89%e7%89%88%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b010/