早期(编译期)优化——语法糖的味道

本文探讨Java泛型与C#泛型的区别,重点分析Java泛型的类型擦除特性,及其对代码设计的影响。同时,介绍了Java中的自动装箱、拆箱机制,以及条件编译的实现方式。


一、泛型与类型擦除

1.1 泛型与类型擦除

Java中的泛型与C#中的泛型区别

Java中的泛型是伪泛型,而C#中的泛型是真泛型。Java中的泛型类型在编译成字节码后,是不区分类型的,都被替换为原生类型了(泛型擦除),并且在相应地方插入了强制类型转换。而C#中的泛型在编译后是真正事实存在的(泛型膨胀)。

Java中泛型例子

public static void main(String argc[]) {
       Map<String, String> map = new HashMap<String, String>();
       map.put("hello", "你好");
       map.put("how are you", "吃了没?");
       System.out.println(map.get("hello"));
       System.out.println(map.get("how are you"));
   }

使用反编译工具后

 public static void main(String[] var0) {
        HashMap var1 = new HashMap();//泛型不见了,被替换为原生类型了
        var1.put("hello", "你好");
        var1.put("how are you", "吃了没?");
        System.out.println((String)var1.get("hello"));//这儿的强制类型转换
        System.out.println((String)var1.get("how are you"));//这儿的强制类型转换
    }

JDK设计团队为什么要用类型擦除实现Java泛型呢?

可能是实现简单、兼容性吧!!!

Java泛型丧失的优雅

import java.util.ArrayList;

public class Test {
    public static String method(ArrayList<String> list) {
        System.out.println("invoke method(ArrayList<String> list)");
        return "";
    }

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

它竟然可以运行,这不是它违法了重载规范。而是在字节码层面特征标签包括方法返回类型和受查异常表。并且,在Java层面,ArrayList<String>与ArrayList<Integer>是两个不同的类型(如果是一样类型的话,那就会报’method(ArrayList)’ is already defined in ‘Test’)。不过这样的话会报’method(ArrayList)’ clashes with ‘method(ArrayList)’; both methods have same erasure错误(有相同的泛型擦除),此时我们使得返回类型不同就行了。

二、自动装箱、拆箱与遍历循环

例子:

import java.util.Arrays;
import java.util.List;

public class Test {
    public static void main(String argc[]) {
        List<Integer> list= Arrays.asList(1,2,3,4);//语法糖:变长参数、自动装箱、泛型
        int sum=0;
        for(int i:list){//语法糖:自动拆箱、循环遍历
            sum+=i;
        }
        System.out.println(sum);
    }
}

用jd-gui反编译的结果:

import java.io.PrintStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

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

当然,语法糖虽甜,但是会长蛀牙哦!!!
例子:

public class Test {
    public static void main(String argc[]) {
       Integer a=1;
       Integer b=2;
       Integer c=3;
       Integer d=3;
       Integer e=321;
       Integer f=321;
       Long g=3L;
       System.out.println(c==d);//1.true
        System.out.println(e==f);//2.false
        System.out.println(c==(a+b));//3.true
        System.out.println(c.equals(a+b));//4.true
        System.out.println(g==(a+b));//5.ture
        System.out.println(g.equals(a+b));//6.false,因为a+b后结果为int,在找重载函数时,自动封装为Integer去了
    }
}

至于1~3和5为什么这样,参考Java自动封装和自动拆箱。至于4和6,要知道一点——重载方法的匹配顺序:
byte–>short–>int–>long–>float–>double–>对应的装箱类–>(现在可以上转型了)。所以a+b后被封装为Integer了。现在又要看equals源码了:

 public boolean equals(Object obj) {
	if (obj instanceof Long) {//很显然Integer不是Long
	    return value == ((Long)obj).longValue();
	}
	return false;
    }

由上面代码就知道了,4为ture,6为false.

当然,顺手粘贴一下反编译结果:

import java.io.PrintStream;

public class Test
{
  public static void main(String[] argc)
  {
    Integer a = Integer.valueOf(1);
    Integer b = Integer.valueOf(2);
    Integer c = Integer.valueOf(3);
    Integer d = Integer.valueOf(3);
    Integer e = Integer.valueOf(321);
    Integer f = Integer.valueOf(321);
    Long g = Long.valueOf(3L);
    System.out.println(c == d);
    System.out.println(e == f);
    System.out.println(c.intValue() == a.intValue() + b.intValue());
    System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
    System.out.println(g.longValue() == a.intValue() + b.intValue());
    System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
  }
}

三、条件编译

C++中提供了预编译(如#include)。java语言并没有使用预处理器(因为java编译器并非一个个地编译Java文件,而是将所有编译单元的语法树顶级节点输入到待处理列表后在进行编译,因此各个文件之间能够互相提供符号信息)。但是,java语言也是可以实现条件编译的——使用条件为常量的if语句。
如:

public class Test {
    public static void main(String argc[]) {
      if(true){
          System.out.println("执行true分支");
      }else{
          System.out.println("执行false分支");
      }
    }
}

反编译之后的结果:

import java.io.PrintStream;

public class Test
{
  public static void main(String[] argc)
  {
    System.out.println("执行true分支");
  }
}

需要注意的是只能使用条件为常量的if语句才能达到上述效果,如果使用常量与其他带有条件判断能力的语句搭配,则可能拒绝编译。如:

 while (false){
           System.out.println("sb");//error:Unreachable statement
  }
<think> 嗯,用户询问的是《深入理解Java虚拟机》第10章和第11章的内容,这两章分别讲前端编译和后端编译优化。用户之前给了一些参考资料,我需要仔细梳理这些内容,组织成清晰的回答。 首先看第10章"前端编译与优化"。根据引用[2]和[5],前端编译主要指javac将Java源码编译为字节码的过程。这里需要强调javac对运行期性能优化有限,主要做的是语法糖处理,比如泛型擦除、自动装箱拆箱、遍历循环等语法特性。特别是引用[5]提到Java泛型采用擦除式实现,只需要编译器改进,不需要改动JVM,兼容性好但功能受限。 引用[4]补充了泛型引入带来的变化,比如新增Signature等属性。这部分可以组织成语法糖处理、语义检查和生成字节码三个阶段,重点说明语法糖的实现原理。 然后是第11章"后端编译与优化"。根据引用[3],后端编译的核心是JIT即时编译器,相比提前编译有三大优势:性能分析制导优化、激进预测性优化和链接时优化。引用[3]详细解释了这些概念,特别是"大胆优化,大不了回退"的特点很有意思。 分层编译模型需要重点说明,包括客户端编译器C1和服务端编译器C2的分工,以及Graal编译器的创新。优化技术部分方法内联最基础也最关键,要放在首位,其他如逃逸分析、循环优化等作为补充。 最后考虑设计相关问题。可以围绕前端编译的语法糖实现、分层编译的演进、即时编译原理对比等角度提问。用户似乎是Java开发者,问题可以稍微深入些,比如问为什么Java不全面转向提前编译。 需要确保专业术语准确,比如说明C1和C2的具体优化层级,以及AOT编译的定位。整体结构按章节分开,每部分先说核心思想再展开细节,最后总结两者的协作关系。 </think>以下是《深入理解Java虚拟机》第10章和第11章的核心内容解析: --- ### **第10章 前端编译与优化:从源码到字节码的转化艺术** #### **1. 前端编译的本质** - **目标**:将Java源码(`.java`)编译为字节码(`.class`),由`javac`实现。 - **定位**:**语法层面的优化**,而非性能优化优化集中在降低编码复杂度与提升开发效率[^2]。 #### **2. 核心优化技术:语法糖解糖** - **泛型擦除** Java泛型通过类型擦除实现,编译后替换为原生类型(如 `List<String>` → `List`),通过`Signature`属性保留泛型信息[^4][^5]。 - *优势*:兼容旧版本JVM,无需修改虚拟机[^5]。 - *局限*:无法像C#那样实现具现化泛型(运行时保留类型)。 - **自动装箱/拆箱** 基本类型与包装类型的自动转换(如 `int ↔ Integer`),编译为`Integer.valueOf()`/`intValue()`调用。 - **遍历循环(for-each)** 转换为迭代器模式(`Iterator`)或传统`for`循环。 - **变长参数** 编译为数组类型(如 `String... args` → `String[] args`)。 - **条件编译** 基于常量布尔值(如`if (false) {...}`)的代码块会被直接剔除。 #### **3. 语义分析与字节码生成** - **语义检查**:验证语法合法性(如类型匹配、变量声明)。 - **符号表生成**:记录变量、方法等符号信息。 - **字节码生成**:将抽象语法树(AST)转换为字节码,辅以少量优化(如常量折叠)。 --- ### **第11章 后端编译与优化:从字节码到机器码的高效跃迁** #### **1. 即时编译器(JIT)的核心价值** - **目标**:将热点代码(频繁执行的字节码)动态编译为本地机器码。 - **优势**(对比提前编译)[^3]: - **性能分析制导优化**:基于运行时监控数据动态优化(如方法调用频率)。 - **激进预测性优化**:在概率性预测下大胆优化(失败时退回解释执行)。 - **链接时优化**:支持动态链接和类加载期的优化。 #### **2. 分层编译模型** | 层级 | 编译器 | 优化目标 | 启动速度 | |------|--------|----------|----------| | 第0层 | 解释器 | 快速启动 | 最快 | | 第1层 | C1(客户端编译器) | 局部简单优化(方法内联等) | 快 | | 第2层 | C2(服务端编译器) | 全局激进优化(逃逸分析等) | 慢 | - **协作机制**:热点代码先由C1编译,进一步热点由C2深度优化。 #### **3. 关键优化技术** - **方法内联** 消除方法调用开销(如`getter/setter`内联),是其他优化的基础[^3]。 - **逃逸分析** 判断对象作用域: - 栈上分配:对象未逃逸时直接在栈分配(减少GC压力)。 - 锁消除:对象仅线程内访问时移出同步锁。 - 标量替换:将对象拆解为基本类型局部变量。 - **循环优化** 包括循环展开(减少迭代次数)、循环剥离(移出非循环依赖代码)。 - **死代码消除** 删除永不会执行的代码(如`if (false)`分支)。 #### **4. 提前编译器(AOT)** - **定位**:在程序运行前将字节码编译为机器码(如`jaotc`工具)。 - **适用场景**:启动性能敏感的应用(如短生命周期微服务)。 - **局限**:无法支持动态类加载和运行时性能制导优化[^3]。 --- ### **前后端编译的协作关系** ```mermaid graph LR A[Java源码] -->|前端编译 javac| B[字节码] B -->|解释执行/即时编译| C[机器码] C -->|运行时性能监控| D[热点代码] D -->|JIT深度优化| E[高效本地代码] ``` > **关键分工**: > - **前端**:开发者友好的语法抽象 → 跨平台字节码 > - **后端**:运行时性能优化 → 平台相关高效执行 --- ### **
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值