我是靠谱客的博主 追寻大山,最近开发中收集的这篇文章主要介绍Integer的缓存模式,自动拆装箱,关于128不等于128的问题一、值相等的Integer,==却不成立?二、自动拆/装箱三、其他基本类型的缓存四、结语,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

**本文首发于公众号【看点代码再上班】,建议关注公众号,及时阅读最新文章。**

一定要读的原文:https://mp.weixin.qq.com/s?__biz=MzIwM……

大家好,我是tin,这是我的第19篇原创文章

我们都遇到过Integer a=128,Integer b=128,但a==b不成立的困惑,今天结合源码和Java的拆装箱说一说其中的原由,先上一个目录:

一、值相等的Integer,==却不成立?

二、自动拆/装箱

2.1 自动装箱

2.2 自动拆箱

三、其他基本类型的缓存

四、结语


一、值相等的Integer,==却不成立?

有两个变量a和b,对应分别赋相同的值,但不同情况下的赋值,==号的比较得到的结果却是截然不同。

package com.tin.example.lang;/** * title: IntegerTest * <p> * description: Integer包装类相关测试 * * @author tin @看点代码再上班 on 2021/5/15 下午1:28 */public class IntegerTest {
public static void main(String[] args) throws InterruptedException {
Integer a = 127;
Integer b = 127;
System.out.println("res:" + (a == b));
a = 128;
b = 128;
System.out.println("res:" + (a == b));
a = -128;
b = -128;
System.out.println("res:" + (a == b));
a = -129;
b = -129;
System.out.println("res:" + (a == b));
}}

@示例代码1

当我们运行以上示例代码,输出结果是这样的:

res:true
res:false
res:true
res:false

看到这,我们应该都会觉得奇怪,为什么有些情况下相等,有些情况就不相等了?

我们都知道,==比较的是对象地址引用,不相等说明对象所在的内存地址不一样。

试猜想:相等是因为指向同一个对象,不相等是因为指向不同的对象。

那么,为什么有些值指向同一个对象,有些值却没有指向同一个对象?

要知道真正原因,一看源码便知,源码底下无秘密。

首先,我们要看的是java.lang.Integer#valueOf(int)方法的源码,其实现如下:

/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value.
If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param
i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since
1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

API描述得很清楚了,这个方法始终缓存-128~127之间的值,且,还有可能缓存此范围以外的值。

我们来理解这句话后面的真正原理:

1、始终缓存-128~127之间的值

直接打开java.lang.Integer.IntegerCache的源码。

java.lang.Integer.IntegerCache是Integer的一个内部类,它唯一的作用就是实现本地缓存,其内部有一个static代码块:

其义就在于JVM启动的时候,把-128~127之间的整数放到cache内。

同时也可以看到,放到cache内的都是new Integer()得到的对象。

所以,valueOf方法中IntegerCache.cache[i + (-IntegerCache.low)]的写法也就是读取缓存了,缓存内相同int值对应的是同一个缓存对象。

以上示例中的a=127b=127都能从缓存读取,且读取到的是同一个对象,==比较的结果是true没问题。

这是由源码决定的,-128~127之间的整数已被缓存起来,超过这个范围的int值不在缓存内,通过new Integer(i)实例化,这时==比较的结果自然就是false。

2、还有可能缓存-128~127以外的值

我们可以自定义IntegerCache缓存值范围的上限,比如我可以把上限改为256,也就是支持缓存-128~256之间的整数。

在启动参数中增加以下代码:

-Djava.lang.Integer.IntegerCache.high=256

我们再次运行@示例代码1,得到结果是这样的:

很明显,Integer a = 128和Integer b=128是相等的,==对比的结果是true。

还可以用另一个VM参数设置,达到同样的效果,如下:

-XX:AutoBoxCacheMax=256

支持这样的设置在源码中也有对应的体现:

首先取自定义的值,如果没有自定义则默认127,但如果设置的值小于127,默认也是127,也即,上限最小支持127。

二、自动拆/装箱

什么是自动装箱和拆箱?

装箱就是Java自动将基本类型转换成对应的包装类型,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将包装类型转换成基本类型的过程就叫做拆箱,比如将Integer对象转换成int类型值。因为这里的装箱和拆箱是Java内部机制自动完成,所以就称作为自动装箱和拆箱

基本类型byte, short, char, int, long, float, double 和 boolean 对应的封装类为Byte, Short, Character, Integer, Long, Float, Double, Boolean,它们都有自动拆装箱的过程。

2.1 自动装箱

Integer a = 127语句没有使用到java.lang.Integer#valueOf(int),为什么我却去看这个方法的源码?

原因很简单,Integer a = 127并不是等同于new Integer(127),而是等同于Integer.valueOf(127),其中就涉及到Java的自动装箱原理。

关于Java是什么时候做装箱的问题,我们需要打开查看class的汇编指令代码。

执行以下命令:

javac IntegerTest.javajavap -c IntegerTest

得到以下编码:

Compiled from "IntegerTest.java"
public class com.tin.example.lang.IntegerTest {
public com.tin.example.lang.IntegerTest();
Code:
0: aload_0
1: invokespecial #1
// Method java/lang/Object."<init>":()V
4: return
​
public static void main(java.lang.String[]) throws java.lang.InterruptedException;
Code:
0: bipush
127
2: invokestatic
#2
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush
127
8: invokestatic
#2
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: getstatic
#3
// Field java/lang/System.out:Ljava/io/PrintStream;
15: aload_1
16: aload_2
17: if_acmpne
24
20: iconst_1
21: goto
25
24: iconst_0
25: invokedynamic #4,
0
// InvokeDynamic #0:makeConcatWithConstants:(Z)Ljava/lang/String;
30: invokevirtual #5
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: sipush
128
36: invokestatic
#2
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
39: astore_1
40: sipush
128
43: invokestatic
#2
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
46: astore_2
47: getstatic
#3
// Field java/lang/System.out:Ljava/io/PrintStream;
50: aload_1
51: aload_2
52: if_acmpne
59
55: iconst_1
56: goto
60
59: iconst_0
60: invokedynamic #4,
0
// InvokeDynamic #0:makeConcatWithConstants:(Z)Ljava/lang/String;
65: invokevirtual #5
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
68: bipush
-128
70: invokestatic
#2
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
73: astore_1
74: bipush
-128
76: invokestatic
#2
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
79: astore_2
80: getstatic
#3
// Field java/lang/System.out:Ljava/io/PrintStream;
83: aload_1
84: aload_2
85: if_acmpne
92
88: iconst_1
89: goto
93
92: iconst_0
93: invokedynamic #4,
0
// InvokeDynamic #0:makeConcatWithConstants:(Z)Ljava/lang/String;
98: invokevirtual #5
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
101: sipush
-129
104: invokestatic
#2
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
107: astore_1
108: sipush
-129
111: invokestatic
#2
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
114: astore_2
115: getstatic
#3
// Field java/lang/System.out:Ljava/io/PrintStream;
118: aload_1
119: aload_2
120: if_acmpne
127
123: iconst_1
124: goto
128
127: iconst_0
128: invokedynamic #4,
0
// InvokeDynamic #0:makeConcatWithConstants:(Z)Ljava/lang/String;
133: invokevirtual #5
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
136: return
}

两个关键命令语句解释如下:

  • bipush 127:表示当 int 取值 -128~127 时,JVM 把对应常量压入栈中。

  • invokestatic #2:invokestatic是方法调用的字节码指令,#2表示符号引用在常量池中的索引号,根据这个索引号检索常量表,可以查到最终表示的是一个字符串字面量,例如java/lang/Integer.valueOf:(I)Ljava/lang/Integer,这个就是方法的符号引用。

为了方便理解字节码,javap反编译的字节码都会给符号引用加注释,注释标明该符号引用的最终表示值,例如java/lang/Object."<init>":()V。

符号引用(Symbolic References)是一个用来无歧义地标识一个实体(例如方法/字段)的字符串,在运行期它会翻译为直接引用(Direct Reference)。对于方法来说,就是方法的入口地址。

所以,方法调用的本质是根据方法的符号引用确定方法的直接引用(入口地址)

在Java编译的时候就已经决定了Integer a = 127等同于Integer a = Integer.valueOf(127),所以,我们上文中才会分析Integer.valueOf方法的源码。

2.2 自动拆箱

自动拆箱,和装箱刚好相反。

我们写如下demo代码:

Integer b = 500;int c = 500;System.out.println("res:" + (b == c));

猜都可以猜到了, b == c是成立的。

同样的,我们通过反编译java文件,看看最终的汇编指令是如何的:

在执行到b == c时候,Java把Integer包装类型进行了拆箱,b等同于b.intValue()java.lang.Integer#intValue返回值就是基础类型值,源码如下:

所以,b == c成立。

三、其他基本类型的缓存

除了Integer,其他7种基本类型对应的包装类都有装箱实现,首先看一看各自的valueOf源码:


//boolean原生类型自动装箱成Boolean
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
//byte原生类型自动装箱成Byte
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
//byte原生类型自动装箱成Byte
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
//char原生类型自动装箱成Character
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
//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);
}
//int原生类型自动装箱成Long
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
//double原生类型自动装箱成Double
public static Double valueOf(double d) {
return new Double(d);
}
//float原生类型自动装箱成Float
public static Float valueOf(float f) {
return new Float(f);
}

从valueOf方法就可以看得出来,除了Boolean、Double和Float没有缓存以外,其余的包装类型都有缓存实现。各个包装类型对应的缓存值范围如下:

基本类型大小包装类型缓存范围是否支持自定义缓存范围
boolean6bitBloolean//
char8bitCharacter0~127
byte8bitByte-128~127
short16bitShort-128~127
int32bitInteger-128~127支持
long64bitLong-128~127
float32bitFloat//
double64bitDouble//

四、结语

我是tin,一个在努力让自己变得更优秀的普通工程师。自己阅历有限、学识浅薄,如有发现文章不妥之处,非常欢迎加我提出,我一定细心推敲并加以修改。

看到这里请安排个“三连”(分享、点赞、在看)再走吧,坚持创作不容易,不要白嫖,你的正反馈是我坚持输出的最强大动力,谢谢!

最后别忘了关注我!⏬⏬⏬

最后再附上原文链接:
https://mp.weixin.qq.com/s?__biz=MzIwM……

最后

以上就是追寻大山为你收集整理的Integer的缓存模式,自动拆装箱,关于128不等于128的问题一、值相等的Integer,==却不成立?二、自动拆/装箱三、其他基本类型的缓存四、结语的全部内容,希望文章能够帮你解决Integer的缓存模式,自动拆装箱,关于128不等于128的问题一、值相等的Integer,==却不成立?二、自动拆/装箱三、其他基本类型的缓存四、结语所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(33)

评论列表共有 0 条评论

立即
投稿
返回
顶部