jvm早期(编译期)优化(一)泛型与类型擦除

    博主最近在看周志明的《深入理解Java虚拟机第2版》这本经典书籍,看到“早期(编译期)优化”章节中的“Java语法糖”部分时感触颇深,感觉非常有必要针对java语法糖做一下总结和记录,以便深入理解这些“糖衣”背后的运行原理

早期(编译期)指的是通过javac编译器把java源码编译为class字节码的过程。

编译期优化(早期优化)

  1. 为了保证JRuby,Groovy等语言编译的字节码也能得到性能优化,JVM将性能优化放在了后期的运行时优化,即JIT运行时编译优化中

  2. 编译期优化主要为语法糖,用来实现Java的各种新的语法特性,比如泛型,自动装箱/拆箱,条件编译

  3. Java语法糖:与字节码无关,编译后会去掉它们。作用仅仅为方便码农写代码,以及将运行时异常在编译期及早发现(如泛型的使用)

   博主准备分3次来分别介绍java语法糖中的泛型,自动装箱/拆箱,条件编译。我们先从“泛型”开始


 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

    泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

首先要明确一点:泛型只在编译期有效,在运行期是无效的

一个例子:

public static void main(String[] args) {
        //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型        
        Map<String,String> map=new HashMap<String, String>();
        map.put("name","liy");
        map.put("sex","man");
        System.out.println(map.get("name"));
        System.out.println(map.get("sex"));
    }

以上代码经过编译生成对应的class文件,然后利用java字节码反编译工具(博主用的是DJ Java Decompiler)进行反编译后生成以下源码:

以下就是实际运行中编译器对泛型的处理,主要用到了java的2个特性:

  1. object类是所有类的父类(Map.get(key)方法返回的就是一个object对象);
  2. 根据java的类型装换规则,object类可以转换成其任意子类(也就是说可以转换为任意对象);
public static void main(String args[]) {
        Map map = new HashMap();//类型擦除
        map.put("name", "liy");
        map.put("sex", "man");
        System.out.println((String)map.get("name"));//类型强制转换
        System.out.println((String)map.get("sex"));//类型强制转换
    }

使用泛型主要有2个好处:

  1. 不用显示的进行强制类型转换,提高编码效率;
  2. 在编译阶段就可以发现一些运行期的潜在危险(比如运行中强制类型转换失败报的java.lang.ClassCastException异常);

扩展:博主当时去京东面试的时候,二面的一个面试官就问了一个关于泛型的问题。

大概意思是这样:定义一个如下的hashMap对象

Map<String,String> hashMap=new HashMap<String, String>()

问:怎么可以把一个int类型的数给存到这个hashMap中,除了简单的将int转换为String之外还有没有其他办法?

当时博主听了真的是一脸懵逼,不过在面试官的不断引导下才勉强答出,后来回到家赶紧上机试了一把。

思路如下:这里主要用到了泛型的生命周期(泛型只在编译期有效)这个特性。

因为hashMap容器添加了String泛型的限制,所以想在编译期间打破限制是不可能的。但是我们可以在java程序运行期间打破这个限制!所以我们可以利用java 的反射机制,在程序运行期间调用hashMap容器的put方法来实现我们的目的!

博主的实现源码如下:

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		Map<String,String> hashMap=new HashMap<String, String>();
		//通过完整的类名(包名.类名)获得HashMap对应的的Class对象
		Class<?> HashMapclass =Class.forName("java.util.HashMap");
		//由于不知道具体方法的具体参数类型所以这里先获取所有方法,然后筛选需要测试的方法
		Method[] methods = HashMapclass.getMethods();
		Method put=null;
		for (Method method : methods) {
			if(method.getName().equals("put")){
				put=method;
			}
		}
		put.invoke(hashMap,new Object[]{"name","liyy"});
		put.invoke(hashMap,new Object[]{"age",27});
		System.out.println(hashMap);
	}

System.out.println(hashMap)最终会输出 {name=liyy, age=27},证明确实实现了我们的目的。

我们也可以通过debug模式来查看一下hashMap的内部结构来再次验证一下


通过debug模式可以清晰的看到hashMap中有一个String类型的变量“liyy”和一个Integer类型变量27。

最后,我们顺利的通过java的反射机制打破了泛型的限制!

本篇博客作为博主的自我知识总结,同时也希望对看到这篇文章的小伙伴们有所帮助!

由于本人能力有限,如果文章有所疏漏或错误还请在评论区提出,大家一起成长!共勉!


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值