写在前面
当我们开发的越久,越能体会到基础知识的重要性。抽空捋一下 JDK 源码,权当查漏补缺。读完之后,你会发现 JDK 源码真的会给你很多惊喜。
Java 是面向对象的编程语言,万物皆对象,但是为了编程的方便还是引入了基本数据类型,为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class)。Integer 是基本数据类型 int 的包装类,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
为什么需要引入 Integer(包装类)
在 Java 中,万物皆对象,所有的操作都要求用对象的形式进行描述。但是 Java 中除了对象(引用类型)还有八大基本类型,它们不是对象。那么,为了把基本类型转换成对象,最简单的做法就是将基本类型作为一个类的属性保存起来,也就是把基本数据类型包装一下,更加符合面向对象的思想,这也就是包装类的由来。
创建 Integer
创建 Integer 的方式:
Integer a = 1; Integer b = new Integer(2); Integer c = Integer.valueOf(3); 复制代码
第一种较为常见,后两种看似更符合 java 语法的概念,众所周知,java 类型分为基本类型和对象类型两种,基本类型为 char,int,double 等,对象类型有 jdk 对象和自己创建的对象,所以 Integer 属于对象类型,int 属于基本类型,对 jvm 而言,类似于 Integer a = 1 这种将基本类型赋值给对象类型的操作是不允许的,但是在日常开发过程中使用这种创建对象的方式并不会报错,原因是编译器的语法糖技术,(语法糖是指在不改变 jvm 语法的情况下,用于方便编程人员编码的操作)。
Integer 缓存
在 Integer 类内部定义了一个私有的静态类 IntegerCache,用来实现 Integer 的缓存常量池。它负责存储(high - low)个静态 Integer 对象,并且在静态代码块中初始化。默认范围是【-128,127】,所以这里默认只实例化了 256 个 Integer 对象,当 Integer 的值范围在【-128,127】时则直接从缓存中获取对应的 Integer 对象,不必重新实例化。这些缓存值都是静态且 final 的,避免重复的实例化和回收。
如果不去配置虚拟机参数,这个值不会变。配合 valueOf(int) 方法,可以节省创建对象造成的资源消耗。另外如果想改变这些值缓存的范围,在启动 JVM 时可以通过 -Djava.lang.Integer.IntegerCache.high=xxx 就可以改变缓存值的最大值。
直接深入源码进行分析:
// Integer类中私有的静态类 承载 cache 的实现 private static class IntegerCache { static final int low = -128;// 最小支持为-128 static final int high;// 最大支持 static final Integer cache[];// 用来装载缓存 常量池 static { // -128~127 这个范围的整数值是使用最广泛的 int h = 127; // Java8 中可以通过调整JVM启动参数来设置最大值 // 根据应用程序的实际情况 灵活的调整来提高性能 String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); // 获取较大者 i = Math.max(i, 127); // 设置最大值不能超过 Inter.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // 如果该值配置错误则忽略该参数配置的值,使用默认范围-128~127 } } high = h; // 初始化数组容量为127 + 128 + 1(以默认区间为参考) cache = new Integer[(high - low) + 1]; int j = low; // 缓存通过 for 循环来实现,创建范围内的整数对象并存储到 cache 数组中 // 程序第一次使用 Integer 的时候需要一定的额外时间来初始化该缓存 for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} } 复制代码
hashCode()
@Override public int hashCode() { return Integer.hashCode(value); } public static int hashCode(int value) { return value; } 复制代码
即 Integer 的 hashCode 值返回对象本身的 value 值。
equals()
public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; } public int intValue() { return value; } 复制代码
equals 方法比较的是两个对象的 value 值,即两个 Integer 对象只要逻辑上数值一致,则 equals 方法返回 true。
Integer 和 int 区别
1、Integer 是 int 的包装类,int 则是 java 的一种基本数据类型。
2、Integer 变量必须实例化后才能使用,而 int 变量不需要。
3、Integer 实际是对象的引用,当 new 一个 Integer 时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值。
4、Integer 的默认值是 null,int 的默认值 0。
关于 Integer 和 int 比较
通过分析六种不同类型的比较,进而真正弄懂 Integer 与 int 的区别,这也是面试中经常会问到的地方。
一、两个 Integer 对象通过 == 比较
Integer a = new Integer(100); Integer b = new Integer(100); System.out.println(a == b); // 输出false 复制代码
因为 a 和 b 是通过对 Integer 对象的引用,进行 new 操作产生了两个内存地址不同的对象,对象之间使用 == 实际上是在比较地址,所以是 false。
二、两个对象通过 equals 方法比较
Integer a = new Integer(100); Integer b = new Integer(100); System.out.println(a.equals(b)); // 输出true 复制代码
当调用 equals 方法进行比较时,只要两个对象的值相同,则返回 true。Integer 重写了 equals 方法,核心逻辑是判断对象表示的 value 值是否相同。
三、基本类型和 Integer 类型 通过 == 比较
Integer a = new Integer(100); int b = 100; System.out.println(a == b); // 输出true 复制代码
因为 Integer 和 int 在进行比较时,java 会将 Integer 自动拆包装为 int 类型,所以实际上是两个 int 变量进行比较,如果值相等的话,则结果为 true。
四、在缓存范围内的比较
Integer a = 100; Integer b = 100; System.out.println(a == b); // 输出true System.out.println(a.equals(b)); // 输出true 复制代码
对应非 new 生成的 Integer 对象,Java 会通过自动装箱机制,调用 Integer 的 valueOf 方法将 int 类型转为 Integer 类型。如果该 int 值在【-128,127】区间范围内,则生成的对象引用都会指向 Integer 内部的常量池中对应的值的对象,因此两个引用指向的内存地址也相同即 == 时返回 true,调用 equals 时比较的是 value 值,因此也返回 true。
五、在缓存范围外的比较
Integer a = 128; Integer b = 128; System.out.println(a == b); // 输出false System.out.println(a.equals(b)); // 输出true 复制代码
非 new 生成的 Integer 对象,如果 int 值超出了【-128,127】这个区间,则不会使用常量池中的对象,由 valueOf 方法可知会重新 new 一个 Integer 对象。因此两个引用 a 和 b,调用 == 比较时是比较的对象内存地址,所以返回 false;但是调用 equals 方法比较的是 value 值,因此返回 true。
六、new 生成的 Integer 对象与直接赋值的 Integer 对象的比较
Integer a = new Integer(100); Integer b = 100; System.out.println(a == b); // 输出false System.out.println(a.equals(b)); // 输出true 复制代码
当非 new 生成的对象和 new 生成的对象进行比较时,非 new 生成的对象根据其 value 值是否在默认缓存区间内而选择是否复用对象,通过 new 生成的对象是在堆中重新开辟的内存空间,因此两者指向的内存地址肯定不一样,所以调用 == 时返回 false;equals 方法比较对象的逻辑值 value,因此返回 true。
开发建议
1、int 是基本数据类型,只占用 4 个字节,Integer 是一个对象,当表示一个值时 Integer 占用的内存空间要高于 int 类型,从节省内存空间考虑,建议使用 int 类型。
2、Integer 类型必须进行初始化后才能使用,否则会引起 NullPointerException 异常。
3、针对一些特殊的场景比如考试成绩分为没有参加考试和成绩为 0,int 的默认值为 0,显然不合适这种场景;Integer 可以区分出未赋值和值为 0 的区别。
4、使用 Integer 类型时,建议采用直接赋值的形式而不是通过 new 产生新对象,提高对内存的利用率。
Integer a = 100; 替代 Integer a = new Integer(100); 复制代码
5、当程序中大量使用数值时,可以根据实际情况适当扩展常量池缓冲区的区间上限,修改 JVM 的启动参数 -Djava.lang.Integer.IntegerCache.high=xxx,进而节省内存,提升性能。
6、当使用 Integer 类型时,在进行两个对象比较的时候,推荐使用 equals 方法,而不是直接调用 “==”。
扩展
Java 中还有与 Integer 类似的是 Long ,它也有一个缓存,在区间【-128,127】范围内获取缓存的值,而 Long 与 long 比较的时候先转换成 long 类型再做值的比较。
Double 类型,它没有缓存,但是当 Double 与 double 比较的时候会先转换成 double 类型,再做值的比较。
结语
有时候往往越简单的知识越容易掉坑里,所以要保持自己的求知欲,不断巩固的基础,才能让自己在面试的时候不会栽跟头。