Java:基本类型和包装类

Java:基本类型和包装类
chify前几天同事遇到了一个很奇怪的 NullPointerException 异常,看她郁闷的蛮久都没有解决,也帮忙分析了一下,情况大致如下:
看得她一头雾水,在帮其初步分析也没想到是啥问题,因为是批量代码,没有相关文件不好本地测试,让其远程 debug 跟了一下代码,发现还是报空指针异常,最后怀疑是服务器上 jar 包没有更新到,拿下 jar 包反编译后,才恍然大悟(反编译如下图),是自己忽略 Person 类和 Student 类的类型了,一个是 Integer ,一个是 int 基本类型,1.5 之后的 JDK 在编译的时候会有自动拆装箱机制!这里记录一下 Java 中包装类型相关的知识点,做个小总结。
基本类型和包装类
Java 中 8 种基本类型对应的包装类型如下表:
| 基本类型 | 大小 | 包装类型 |
|---|---|---|
| boolean | 1 Byte | Boolean |
| char | 2 Byte | Character |
| byte | 1 Byte | Byte |
| short | 2 Byte | Short |
| int | 4 Byte | Integer |
| long | 8 Byte | Long |
| float | 4 Byte | Float |
| double | 8 Byte | Double |
自动装箱和拆箱
以 Integer 包装类为例(其他的包装类也是类似),看看其自动拆装箱是如何实现的,写段 代码如下图:
由上图可知,自动装箱时,JDK 在编译的时候会调用 Integer.valueOf(int) 这个方法将 int 转为 Integer,而在自动拆箱的时候 Integer 对象会调用 intValue() 方法(Short 类则为 shortValue()),返回 Integer 所持有的 int 成员变量,intValue() 没什么好看的,就是返回了 Integer 对象所持有的 int 成员变量。valueOf(int) 函数倒是值得研究一下,截图如下:
可知在 int 值在 -127~ IntegerCache.high(VM 参数 -Djava.lang.Integer.IntegerCache.high=*** 可以设置 high 的值)之间时,将会返回一个Integer 内部类 IntegerCache 里 cache 数组缓存下来的一个 Integer 对象,不是在这个范围外,才会返回一个新的 Integer 对象。IntegerCache 内部类如下图:
IntegerCache 这个内部类,有个静态 Integer 类型 cache 数组,静态代码块会给 cache 数组初始化值,缓存符合要求的 Integer 值。这里也就引发了一个初级面试中经常会遇到的问题,如下图:
首先 == 在 Java 里,基本类型比较的是值是否相等,而引用型类型则是比较内存地址是否相同, Integer 是个引用类型,默认情况下,-128到127 范围内的 int 值在自动装箱时调用 valueOf(int)方法返回的都是 cache 里缓存好的对象,所以不管是 i ,还是 i2 引用,其实都是指向缓存数组 cache 早已经定义好的 Integer 对象,所以 i == i2 是 true ;而 128 不在 -128到127 的范围内,所以 valueOf(int) 返回的是 new Integer(int),返回一个新的 Integer 对象,所以 i3 == i4 也就是 false 了。
还有另外一种情况,代码截图如下:
这里的 i3 引用其实就是和自动装箱无关了,它就是用 Integer 的构造方法生成了一个新的 Integer 对象,和 cache 数组缓存的又是不同的对象,i2 == i3 也就等于 false 了。
注:除 Integer 包装类外,Byte、Short、Long、Character(char)包装类都是有类似于 Integer 的缓存机制的,Float、Double 由于是浮点类型的,JDK 中并未实现这种缓存机制。至于 Boolean 类型的的话又是一个例外,翻看它的实现大致就可以知道啥原理了,其实现如下:
自动装箱流程
flowchart TD
A["int primitiveValue = 100"] --> B["编译器插入 Integer.valueOf()"]
B --> C{"value 是否在 -127 ~ 127 范围内?"}
C -->|是| D["返回 IntegerCache.cache 中缓存的对象"]
C -->|否| E["new Integer(value),创建新对象"]
D --> F["Integer wrapper = 缓存对象"]
E --> F
性能影响
自动拆装箱虽然方便,但在高频场景下会带来性能开销:
- 装箱:每次装箱都会调用
valueOf(),若超出缓存范围还会创建新对象,增加 GC 压力。 - 拆箱:每次拆箱都会调用
intValue()等方法,虽然开销小,但在循环中大量调用仍不可忽视。
1 | // 反面示例:循环中频繁装箱 |
在集合操作中也要特别注意:ArrayList<Integer> 的 get() 返回的是包装类型,大量取值操作会产生拆箱开销。
建议:在性能敏感的场景(如大循环、密集计算)中,优先使用基本类型;仅在需要使用泛型或集合时才使用包装类型。
包装类常用方法
其中包装类中拥有共同的方法如下:
- 带有基本值参数并创建包装类对象的构造函数。如利用
Integer包装类创建对象,Integer obj=new Integer(145); - 带有字符串参数并创建包装类对象的构造函数.如:
new Integer("-45.36"); - 可生成对象基本值的
typeValue方法,如:obj.intValue(); - 将字符串转换为基本值的
parseType方法,如:Integer.parseInt(args[0]); - 生成哈希表代码的
hashCode方法,如:obj.hashCode(); - 对同一个类的两个对象进行比较的
equals()方法,如:obj1.equals(obj2); - 生成字符串表示法的
toString()方法,如:obj.toString().

















