Java基础之 Integer 类源码分析

时间:2023-01-14 21:57:21


Integer 类


源码说明

Java中Integer是基本数据类型int的包装类。也就是每一个Integer对象包含一个int类型的属性,是抽象类Number类的子类,位于java.lang包下。

部分源码:

public final class Integer extends Number implements Comparable<Integer> {

@Native public static final int MIN_VALUE = 0x80000000;
@Native public static final int MAX_VALUE = 0x7fffffff;

private final int value;

public Integer(int value) {
this.value = value;
}

public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}
}

通过源码可以看出,最小值用十六进制表示为​​0x80000000​​​。使用二进制表示为​​1000 0000 0000 0000 0000 0000 0000 0000​​​.最大值用十六进制表示为​​0x7fffffff​​​。 使用二进制表示为 ​​0111 1111 1111 1111 1111 1111 1111 1111​​. 由于一个字节占用8位,而Integer的最大值和最小值都是占用32位,由此而知,int类型是占用4个字节的。最小值为 -2的31次方。最大值为2的31次方-1.

为何​​0x80000000​​可以表示最小值,可以参考这篇文章 ​​原码,反码和补码​​.

面试题

我们首先来看一道题:

public class IntegerTest {
public static void main(String[] args) {
Integer i1 = 127;
Integer i2 = new Integer(127);
Integer i3 = 127;
System.out.println(i1 == i2);
System.out.println(i1.equals(i2));
System.out.println(i1 == i3);
Integer ii1 = 128;
Integer ii2 = 128;
System.out.println(ii1 == ii2);
}
}

结果是:

false
true
true
false

不知道你们有没有答对呢?下面我们来看一下反编译过来的代码:

public class IntegerTest {
public static void main(String[] args) {
Integer i1 = Integer.valueOf(127);
Integer i2 = new Integer(127);
Integer i3 = Integer.valueOf(127);
System.out.println((i1 == i2));
System.out.println(i1.equals(i2));
System.out.println((i1 == i3));
Integer ii1 = Integer.valueOf(128);
Integer ii2 = Integer.valueOf(128);
System.out.println((ii1 == ii2));
}
}

可以看出 i1 和 i3 都调用了Integer.valueOf方法,这其实就是自动装箱的过程.Java基本数据自动转为包装类的过程称为自动装箱

我们看一下valueOf()方法的源码:

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

我们看到其中有一个IntegerCache类,我们再看一下源码:

private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}

可以看出其中有一步是:​​sun.misc.vm.getSavedProperty("java.lang.Integer.IntegerCache.high")​​.

实际上在Java5中引入这个特性的时候,范围固定在-128到127之间。后来在Java6后,最大映射到 java.lang.Integer.IntegerCache.high, 可以使用JVM的启动参数设置最大值。(通过JVM的启动参数 -XX:AutoBoxCacheMax=size 修改)

我们暂时把自定义部分去掉,如下:

private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
int h = 127;
high = h;
cache = new Integer[(high - low) + 1];//127-(-128)+1=256
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

以上就是Integer缓存池的源代码。为什么长度要加上1呢?因为需要把整数0算上。我们通过源码可以看到,对于Integer类型,内部有一个缓存池,实现把-128到127之间的对象先创建出来了。所以如果是通过调用valueOf()方法创建并且在-128到127之间的数,自动返回缓存中的对象,而由于自动装箱调用的就是valueOf方法。

下面我们再次来看下面的题:

Integer i1 = 127;
Integer i2 = new Integer(127);
Integer i3 = 127;
System.out.println(i1 == i2);//false
System.out.println(i1.equals(i2));//true
System.out.println(i1 == i3);//true
Integer ii1 = 128;
Integer ii2 = 128;
System.out.println(ii1 == ii2);//false

由于i1,i3 是自动装箱,调用了Integer.valueOf(),同时在默认返回下-128–127范围内,所以走的是缓存,故i1==i3为true (等于号是直接比较地址的)。而i2是自己创建对象,不是调用valueOf方法的,那么就不会走缓存,所以 i1 == i2 是false。那么为什么i1.equals(i2) 也会是true。我们看一下Integer类 valueOf()源码:

public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}

通过源码可以看出,首先判断传入的是否为Integer 类型。

  • 是:判断其intValue()值与当前对象的value是否相等
  • 是:返回true。
  • 否:返回false。
  • 其他:返回false。

总结,使用Integer i = 9 这种方式来定义; 只要给值在-128到127之间,调用自动装箱方法valueOf,这个值都是作为下标处理的,取的是Integer缓存池的对象。

装箱拆箱过程

自动装箱:

Integer i = 2;

上面的代码会自动装箱。编译之后如下:

Integer i = Integer.valueOf(2);

自动拆箱:

int i = 2;
Integer ii = 2;
System.out.println(i == ii);

上面的代码经过自动拆箱。即编译之后变成如下:

int i = 2;
Integer ii = Integer.valueOf(2);
System.out.println(i == ii.intValue())

试题+1

在牛客上看到这样一个问题,如下:

【JAVA】条件 a == (Integer) 1 && a == (Integer) 2有可能为true吗?

代码可以表示为:

public static void main(String[] args){
int a = 1;
if(a == (Integer)1 && a == (Integer)2){
System.out.println("true");
}
}

乍一看,肯定不可能啊,但是仔细想想呢?

我们先看一下其反编译过来的代码:

int a = 1;
if (a == Integer.valueOf(1).intValue() && a == Integer.valueOf(2).intValue())
System.out.println(true);

可以看出来,​​a == (Integer)1​​ ,这句其实是先将1进行装箱,再进行拆箱与a进行比较。但是由于自动装箱走缓存同时1和2又在-128到127之间,所以我们可以从缓存进行入手。

Java基础之  Integer 类源码分析


如下:

public static void main(String[] args) throws Exception {
//1.获取Integer类的Class类对象
Class<Integer> integerClass = Integer.class;
//2.获取Integer类内部的类的对象,只有一个,取0,就是IntegerCache类
Class<?> integerCacheClass = integerClass.getDeclaredClasses()[0];
//3.获取IntegerCache类的cache属性对象
Field cacheField = integerCacheClass.getDeclaredField("cache");
//4.设置可访问
cacheField.setAccessible(true);
//5.由于IntegerCache类是static的,在加载的时候会走IntegerCache类中的static代码块
//把cache数组给初始化,所以我们后面是可以直接操作的
Integer[] o = (Integer[])cacheField.get(integerCacheClass);
//0 下标是示-128对象,127下标的是-1对象,128下标的是0对象,129和130下标的分别是1对象 和 2对象
//6. 将下标为130的2对象 更新为 下标为129的1对象.
o[130] = o[129];
System.out.println(Arrays.toString(o));
int a = 1;
//此时由于使用了类型装换,会进行自动装箱,再拆箱比较,所以1和2获取到的都是缓存中的1对象。再拆箱比较,就成立了。
if (a == (Integer)1 && a == (Integer)2){
System.out.println(true);
}
}

上面的代码即可在最后输入​​true​​. 主要是利用反射来修改IntegerCache的内容。如果前面自动装箱走缓存理解了,那么这里也可以理解,主要是配合反射来修改原来已经初始化好的缓存中的内容。

数组截图:

Java基础之  Integer 类源码分析

另:

byte类型占用1个字节。

short类型占用2个字节。

char类型占用2个字节。

int类型占用4个字节。

long类型占用8个字节。

float类型占用4个字节。7到8位有效数字。

double类型占用8个字节。16到17位有效数字。