自动装箱与自动拆箱

首先要提到的便是 Java 的自动装箱(auto-boxing)和自动拆箱(auto-unboxing)。

我们知道,Java 语言拥有 8 个基本类型,每个基本类型都有对应的包装(wrapper)类型。

之所以需要包装类型,是因为许多 Java 核心类库的 API 都是面向对象的。举个例子,Java 核心类库中的容器类,就只支持引用类型。

当需要一个能够存储数值的容器类时,我们往往定义一个存储包装类对象的容器。

对于基本类型的数值来说,我们需要先将其转换为对应的包装类,再存入容器之中。在 Java 程序中,这个转换可以是显式,也可以是隐式的,后者正是 Java 中的自动装箱。

public int foo() {
ArrayList<Integer> list = new ArrayList<>();
list.add(0);
int result = list.get(0);
return result;
}

以上图中的 Java 代码为例。我构造了一个 Integer 类型的 ArrayList,并且向其中添加一个 int 值 0。然后,我会获取该 ArrayList 的第 0 个元素,并作为 int 值返回给调用者。这段代码对应的 Java 字节码如下所示:

public int foo();
Code:
0: new java/util/ArrayList
3: dup
4: invokespecial java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: iconst_0
10: invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: invokevirtual java/util/ArrayList.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: iconst_0
19: invokevirtual java/util/ArrayList.get:(I)Ljava/lang/Object;
22: checkcast java/lang/Integer
25: invokevirtual java/lang/Integer.intValue:()I
28: istore_2
29: iload_2
30: ireturn

当向泛型参数为 Integer 的 ArrayList 添加 int 值时,便需要用到自动装箱了。在上面字节码偏移量为 10 的指令中,我们调用了 Integer.valueOf 方法,将 int 类型的值转换为 Integer 类型,再存储至容器类中。

public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

这是 Integer.valueOf 的源代码。可以看到,当请求的 int 值在某个范围内时,我们会返回缓存了的 Integer 对象;而当所请求的 int 值在范围之外时,我们则会新建一个 Integer 对象。

在介绍反射的那一篇中,我曾经提到参数 java.lang.Integer.IntegerCache.high。这个参数将影响这里面的 IntegerCache.high。

也就是说,我们可以通过配置该参数,扩大 Integer 缓存的范围。Java 虚拟机参数 -XX:+AggressiveOpts 也会将 IntegerCache.high 调整至 20000。

奇怪的是,Java 并不支持对 IntegerCache.low 的更改,也就是说,对于小于 -128 的整数,我们无法直接使用由 Java 核心类库所缓存的 Integer 对象。

当从泛型参数为 Integer 的 ArrayList 取出元素时,我们得到的实际上也是 Integer 对象。如果应用程序期待的是一个 int 值,那么就会发生自动拆箱。

在我们的例子中,自动拆箱对应的是字节码偏移量为 25 的指令。该指令将调用 Integer.intValue 方法。这是一个实例方法,直接返回 Integer 对象所存储的 int 值。

泛型与类型擦除

你可能已经留意到了,在前面例子生成的字节码中,往 ArrayList 中添加元素的 add 方法,所接受的参数类型是 Object;而从 ArrayList 中获取元素的 get 方法,其返回类型同样也是 Object。

前者还好,但是对于后者,在字节码中我们需要进行向下转换,将所返回的 Object 强制转换为 Integer,方能进行接下来的自动拆箱。

之所以会出现这种情况,是因为 Java 泛型的类型擦除。这是个什么概念呢?简单地说,那便是 Java 程序里的泛型信息,在 Java 虚拟机里全部都丢失了。这么做主要是为了兼容引入泛型之前的代码。

当然,并不是每一个泛型参数被擦除类型后都会变成 Object 类。对于限定了继承类的泛型参数,经过类型擦除后,所有的泛型参数都将变成所限定的继承类。也就是说,Java 编译器将选取该泛型所能指代的所有类中层次最高的那个,作为替换泛型的类。

class GenericTest<T extends Number> {
T foo(T t) {
return t;
}
}

举个例子,在上面这段 Java 代码中,我定义了一个 T extends Number 的泛型参数。它所对应的字节码如下所示。可以看到,foo 方法的方法描述符所接收参数的类型以及返回类型都为 Number。方法描述符是 Java 虚拟机识别方法调用的目标方法的关键。

T foo(T);
descriptor: (Ljava/lang/Number;)Ljava/lang/Number;
flags: (0x0000)
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: areturn
Signature: (TT;)TT;

既然泛型会被类型擦除,那么我们还有必要用它吗?

我认为是有必要的。Java 编译器可以根据泛型参数判断程序中的语法是否正确。举例来说,尽管经过类型擦除后,ArrayList.add 方法所接收的参数是 Object 类型,但是往泛型参数为 Integer 类型的 ArrayList 中添加字符串对象,Java 编译器是会报错的。

桥接方法

泛型的类型擦除带来了不少问题。其中一个便是方法重写。

总结与实践

今天我主要介绍了 Java 编译器对几个语法糖的处理。

基本类型和其包装类型之间的自动转换,也就是自动装箱、自动拆箱,是通过加入 [Wrapper].valueOf(如 Integer.valueOf)以及 [Wrapper].[primitive]Value(如 Integer.intValue)方法调用来实现的。

Java 程序中的泛型信息会被擦除。具体来说,Java 编译器将选取该泛型所能指代的所有类中层次最高的那个,作为替换泛型的具体类。

由于 Java 语义与 Java 字节码中关于重写的定义并不一致,因此 Java 编译器会生成桥接方法作为适配器。此外,我还介绍了 foreach 循环以及字符串 switch 的编译。

JVM总结-Java语法糖与Java编译器的更多相关文章

  1. 早期(编译器)优化--Java语法糖的味道

    1.泛型与类型擦除 泛型的本质是参数化类型的应用,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口和泛型方法.在泛型没有出现之前,只能通过 ...

  2. Java语法糖1:可变长度参数以及foreach循环原理

    语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...

  3. 转:【深入Java虚拟机】之六:Java语法糖

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/18011009 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家P ...

  4. Java语法糖设计

    语法糖 Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这 ...

  5. 深入理解java虚拟机(十二) Java 语法糖背后的真相

    语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言 ...

  6. java语法糖---枚举

    java语法糖---枚举   在JDK5.0中提供了大量的语法糖,例如:自动装箱拆箱.增强for循环.枚举.泛型等.所谓“语法糖”就是指提供更便利的语法供程序员使用,只是在编译器上做了手脚,却没有提供 ...

  7. Java语法糖(二)

    语法糖之四:内部类 内部类:顾名思义,在类的内部在定义一个类.内部类仅仅是编译时的概念,编译成字节码后,内部类会生成单独的Class文件. 四种:成员内部类.局部内部类.匿名内部类.静态内部类. 1. ...

  8. 【深入Java虚拟机】之六:Java语法糖

    语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家Peter.J.Landin发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使 ...

  9. Java语法糖(一)

    概述 语法糖(Syntactic Sugar):主要作用是提高编码效率,减少编码出错的机会. 解语法糖发生在Java源码被编译成Class字节码的过程中,还原回简单的基础语法结构. 语法糖之一:泛型( ...

随机推荐

  1. linux shell创建目录、遍历子目录

    1.创建目录 代码1: #!/bin/bash#如果没有tmp_dir目录则创建static_dir="/web/fyunw.com/static"if [ ! -d $staic ...

  2. jmeter --- 监控器 Plugins (&jconsole)

    jmeter --- 监控器 Plugins (&jconsole) Jmeter本身没有监控服务器资源的功能,需要添加额外插件 一.监控原理图 二.Jmeter-Plugs下载和安装 官网上 ...

  3. win10:如何开启自带虚拟机

    1.首先要找到控制面板,我们点开windows键,然后选择在所有应用中找到“Windows 系统”,打开之后,我们找到“控制面板”,打开.     2.打开控制面板之后,我们选择程序,如图示.   3 ...

  4. DataFlow编程模型与Spark Structured streaming

    流式(streaming)和批量( batch):流式数据,实际上更准确的说法应该是unbounded data(processing),也就是无边界的连续的数据的处理:对应的批量计算,更准确的说法是 ...

  5. java小程序(课堂作业03)

    使用类的静态字段和构造函数,我们可以跟踪某个类所创建对象的个数.请写一个类,在任何时候都可以向它查询“你已经创建了多少个对象?”. 思路:因为静态初始化块只运行一次,是一个很好的记录次数的方法,定义一 ...

  6. oracle rename数据文件的两种方法

    oracle rename数据文件的两种方法 2012-12-11 20:44 10925人阅读 评论(0) 收藏 举报  分类: oracle(98)  版权声明:本文为博主原创文章,未经博主允许不 ...

  7. pytest.11.生成xml格式的测试报告

    From: http://www.testclass.net/pytest/report/ pytest有非常友好的命令行报告输出,在做用例开发的时候,这是极好的.然而我们在运行用例后经常会需要将测试 ...

  8. lucene 一次查询多个id

    在文本搜索中,有时也需要一次搜索多个id,这里id类似数据库里面的主键. 这个id在索引里面的倒排列表长度往往等于1. 例如:根据id=[1,2,4,6,7]查询索引 最最一般的思路是构造一个bool ...

  9. 修改String中的内容

    例子:有一个字符串"abcdef",现在想让字符串中的字符各自加1,求修改后的字符 String 在Java中是不可修改的. 方法1:将String 变为字符数组,通过修改字符数组 ...

  10. P1507 NASA的食物计划

    传送 01背包又进化了,它变成了二维背包. 既然它多了一个维度,那么我们的f[j]也变成了二维数组f[i][j],其中i表示费用1,j表示费用2 核心方程也相应的变成了f[i][j]=max(f[i- ...