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 缓存数组对象,看起来像各自维护了一个对象池一样。
如有不同意见,欢迎留言交流!!