博主最近在看周志明的《深入理解Java虚拟机第2版》这本经典书籍,看到“早期(编译期)优化”章节中的“Java语法糖”部分时感触颇深,感觉非常有必要针对java语法糖做一下总结和记录,以便深入理解这些“糖衣”背后的运行原理
早期(编译期)指的是通过javac编译器把java源码编译为class字节码的过程。
编译期优化(早期优化)
为了保证JRuby,Groovy等语言编译的字节码也能得到性能优化,JVM将性能优化放在了后期的运行时优化,即JIT运行时编译优化中
编译期优化主要为语法糖,用来实现Java的各种新的语法特性,比如泛型,自动装箱/拆箱,条件编译
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个特性:
- object类是所有类的父类(Map.get(key)方法返回的就是一个object对象);
- 根据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个好处:
- 不用显示的进行强制类型转换,提高编码效率;
- 在编译阶段就可以发现一些运行期的潜在危险(比如运行中强制类型转换失败报的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的反射机制打破了泛型的限制!
本篇博客作为博主的自我知识总结,同时也希望对看到这篇文章的小伙伴们有所帮助!
由于本人能力有限,如果文章有所疏漏或错误还请在评论区提出,大家一起成长!共勉!