Java中的常量池技术,是为了方便快捷地创建(获取)某些对象而出现的。当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。

【1】Integer的小坑

先看个示例:

public static void main(String[] args) {

Integer a = 10;
Integer b = 10;
Integer c = 200;
Integer d = 200;
System.out.println(a==b);//true
System.out.println(c==d);//false
//new 肯定是创建一个新的对象
Integer i1=new Integer(1);
Integer i2=new Integer(1);
//i1,i2分别位于堆中不同的内存空间
System.out.println(i1==i2);//输出false
}

为什么​​a==b true;c==d false​​?

看编译后的class是个什么鬼?

public static void main(String[] args) {
Integer a = Integer.valueOf(10);
Integer b = Integer.valueOf(10);
Integer c = Integer.valueOf(200);
Integer d = Integer.valueOf(200);
System.out.println(a == b);
System.out.println(c == d);
}

​Integer a=10;​​其实就是​​Integer.valueOf(10)​​,这个很重要!

跟一下源码​​Integer.valueOf{}​​:

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

即,如果变量 i 在​​-128-127​​​之间,直接从IntegerCache类的cache[]成员中根据下标拿一个返回。否则,new一个对象返回!!这也就是为什么​​a==b true;c==d false​


那么问题来了,IntegerCache是个什么鬼?

private static class IntegerCache {
static final int low = -128;
//high并没有赋初值,是可以配置的
static final int high;
static final Integer cache[];
// 静态代码块,类加载过程中就会执行
static {
// high value may be configured by property
int h = 127;
//尝试读取配置的high属性
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
//如果不为null,则计算值并赋给h
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
// 把h的值赋给high
high = h;
// 创建缓存数组
cache = new Integer[(high - low) + 1];
int j = low;
// 将范围的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;
}
//...
}

这货是Integer的静态内部类,里面有个一静态常量数组很关键​​static final Integer cache[];​​!!!


再用​​javap -verbose TestA.class​​命令看一下:

C:\Users\12746\Desktop>javap -verbose TestA.class
Classfile /C:/Users/12746/Desktop/TestA.class
Last modified 2018-9-2; size 850 bytes
MD5 checksum c203fb4da7b2a99caad3e1388a87bad9
Compiled from "TestA.java"
public class com.test.classes.TestA
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#29 // java/lang/Object."<init>":()V
#2 = Methodref #30.#31 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#3 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Methodref #34.#35 // java/io/PrintStream.println:(Z)V
#5 = Class #36 // com/test/classes/TestA
#6 = Class #37 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/test/classes/TestA;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 a
#19 = Utf8 Ljava/lang/Integer;
#20 = Utf8 b
#21 = Utf8 c
#22 = Utf8 d
#23 = Utf8 StackMapTable
#24 = Class #17 // "[Ljava/lang/String;"
#25 = Class #38 // java/lang/Integer
#26 = Class #39 // java/io/PrintStream
#27 = Utf8 SourceFile
#28 = Utf8 TestA.java
#29 = NameAndType #7:#8 // "<init>":()V
#30 = Class #38 // java/lang/Integer
#31 = NameAndType #40:#41 // valueOf:(I)Ljava/lang/Integer;
#32 = Class #42 // java/lang/System
#33 = NameAndType #43:#44 // out:Ljava/io/PrintStream;
#34 = Class #39 // java/io/PrintStream
#35 = NameAndType #45:#46 // println:(Z)V
#36 = Utf8 com/test/classes/TestA
#37 = Utf8 java/lang/Object
#38 = Utf8 java/lang/Integer
#39 = Utf8 java/io/PrintStream
#40 = Utf8 valueOf
#41 = Utf8 (I)Ljava/lang/Integer;
#42 = Utf8 java/lang/System
#43 = Utf8 out
#44 = Utf8 Ljava/io/PrintStream;
#45 = Utf8 println
#46 = Utf8 (Z)V
{
public com.test.classes.TestA();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/test/classes/TestA;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=5, args_size=1
0: bipush 10
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush 10
8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: sipush 200
15: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: astore_3
19: sipush 200
22: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
25: astore 4
27: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
30: aload_1
31: aload_2
32: if_acmpne 39
35: iconst_1
36: goto 40
39: iconst_0
40: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
43: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload_3
47: aload 4
49: if_acmpne 56
52: iconst_1
53: goto 57
56: iconst_0
57: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
60: return
LineNumberTable:
line 10: 0
line 11: 6
line 12: 12
line 13: 19
line 14: 27
line 15: 43
line 16: 60
LocalVariableTable:
Start Length Slot Name Signature
0 61 0 args [Ljava/lang/String;
6 55 1 a Ljava/lang/Integer;
12 49 2 b Ljava/lang/Integer;
19 42 3 c Ljava/lang/Integer;
27 34 4 d Ljava/lang/Integer;
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 39
locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ]
stack = [ class java/io/PrintStream, int ]
frame_type = 79 /* same_locals_1_stack_item */
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ]
stack = [ class java/io/PrintStream, int ]
}

常量池(constant_pool)是在编译期被确定,并被保存在已编译的class文件中的一些数据。除了包含代码中所定义的各种基本类型(如 int、long等)和对象型(如 String 及数组)的常量值外,还包含一些以文本形式出现的符号引用(Class中的常量池中数据会在加载的方式放进方法区中的运行时常量池中)。

Java中八种基本类型的包装类的大部分都实现了常量池技术,它们是Byte、Short、Integer、Long、Character、Boolean,另外两种浮点数类型的包装类(Float、Double)则没有实现。

何谓实现常量池技术?第一,基本类型(非包装类型)的值存放在常量池中;第二,包装类型(值在-128-127之间)的那些个对象存放在缓存数组中。取的时候,拿到的是同一个。如Integer a=10;Integer b =10; 此时a==b为true!

另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值在-128到127时才可使用常量池。如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取

Byte、Short、Integer、Long、Character都是使用类似缓存数组来实现常量池技术。Boolean只有两个值,直接声明了两个静态常量,值为对象。

ByteCache

private static class ByteCache {
private ByteCache(){}

static final Byte cache[] = new Byte[-(-128) + 127 + 1];

static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}

Boolean的有点特殊:

public static final Boolean TRUE = new Boolean(true);

/**
* The {@code Boolean} object corresponding to the primitive
* value {@code false}.
*/
public static final Boolean FALSE = new Boolean(false);

Float和Double每次都是new一个新对象出来:

public static Float valueOf(float f) {
return new Float(f);
}

故而,如下两个Double不相等

Double d1=1.0;  

Double d2=1.0;

因为Double 没有实现常量池技术,所以Doubled1=1.0;相当于​​Double d1=new Double(1.0)​​。d2类同,各自new了一个对象,所以d1和d2存放的指针不同,指向的对象不同,所以不相等。


是不是感觉理解了常量池技术?那么问题来了,缓存数组中的对象存放在什么地方?缓存数组又存放在哪里?

再回头看下代码:

private static class ByteCache {
private ByteCache(){}

static final Byte cache[] = new Byte[-(-128) + 127 + 1];

static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}

缓存数组是个静态常量,理论上来说是存放在class文件的常量池,在类加载后会存放进方法区的运行时常量池中。


既然常量是放在常量池中,为什么说yte、Short、Integer、Long、Character、Boolean实现了常量池技术?

个人理解: 它们各自维护了一个static final 缓存数组对象,看起来像各自维护了一个对象池一样。

如有不同意见,欢迎留言交流!!