深入理解java虚拟机第三版读书笔记10

以下是第十章 前端编译与优化的内容

Java中的编译可能是指:

  • 前端编译:把*.java文件转变成*.class文件的过程
  • 即时编译(JIT):运行期把字节码转变成本地机器码的过程
  • 提前编译(AOT):直接把程序编译成与目标机器指令集相关的二进制代码的过程

这一章讲的是前端编译

Javac编译器

javac是一个由java语言编写的程序

Javac的源码与调试

Java6以后,Javac的源码放在了JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/javac

Javac的工作流程

  1. 准备过程:初始化插入式注解处理器。
  2. 解析与填充符号表过程,包括:
    ·词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树。
    ·填充符号表。产生符号地址和符号信息。
  3. 插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段。
  4. 分析与字节码生成过程,包括:
    • 标注检查。对语法的静态信息进行检查。
    • 数据流及控制流分析。对程序动态运行过程进行检查。
    • 解语法糖。将简化代码编写的语法糖还原为原有的形式。
    • 字节码生成。将前面各个步骤所生成的信息转化成字节码。

执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转回到之前的解析、填充符号表的过程中重新处理这些新符号。

Java语法糖的味道

泛型

Java与C#的泛型

Java是 “类型擦除式泛型”,C#是 “具现化式泛型”

Java语言中的泛型只在程序源码中存在,在编译后的字节码文件中,全部泛型都被替换为原来的裸类型,并且在相应的地方插入了强制转型代码。

Java的类型擦除式泛型无论在使用效果上还是运行效率上,几乎是全面落后于C#的具现化式泛型,而它的唯一优势是在于实现这种泛型的影响范围上。

Java选择类型擦除主要是因为遗留代码多,向前兼容。

类型擦除

实现:泛型类原地泛型化变成裸类型

Java是简单粗暴地直接在编译时把ArrayList<Integer>还原回ArrayList,只在元素访问、修改时自动插入一些强制类型转换和检查指令。

类型擦除的缺陷:

  1. 不支持基本类型,例如ArrayList<int>,因为无法实现int和Object互转,只能自动装箱、拆箱使用Integer,导致执行效率低。
  2. 运行期无法取到泛型类型信息,导致不能出现下列操作:
    • if (item instanceof E) { // 不合法,无法对泛型进行实例判断
    • E newItem = new E(); // 不合法,无法使用泛型创建对象
    • E[] itemArray = new E[10]; // 不合法,无法使用泛型创建数组
  3. 方法重载即使是不同泛型类型的参数也不能作为不一样的特征签名。但是返回值类型不同实际是可以正常运行的,虽然特征签名还是一致,但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/