Java Integer的内存存储在堆和常量池中,及String的内存存储

先看代码:

        int i1 = 128;
        Integer i2 = 128;
        Integer i3 = new Integer(128);
        //Integer会自动拆箱为int,所以为true
        System.out.println(i1 == i2);
        System.out.println(i1 == i3);
        System.out.println(i2 == i3);
        System.out.println("**************");
        Integer i4 = 127;//java在编译的时候,被翻译成-> Integer i5 = Integer.valueOf(127);
        Integer i5 = 127;
        Integer i6 = Integer.valueOf(127);
        System.out.println(i4 == i5);//true
        System.out.println(i4 == i6);//true
        Integer i7 = new Integer(127);
        System.out.println(i4 == i7); //false
        Integer i8 = 128;
        Integer i9 = 128;
        System.out.println(i8 == i9);//false
        Integer i10 = new Integer(128);
        Integer i11 = new Integer(128);
        System.out.println(i10 == i11);  //false

测试的结果:

Paste_Image.png

在Java中,对于对象==是比较两个对象的地址。
Integer的存储
1、Integer是int的封装类,当基础变量(int)和Integer进行比较时,Integer会自动拆箱(jdk1.5以上)了后,再去和int进行比较,所以判断i1==i2,i1==i3都为true。
2、对JVM为了节省空间, 当Integer的值落在-128~127之间时,如i4,i5;此时JVM首先检查是否已存在值为127的Integer对象。如果是,则i4,i5直接是引用已存在对象,即i4 = i5。所以判断i4 == i5 为 true
那为什么范围是-128到127呢:java在编译Integer i4 = 127的时候,是被翻译成-> Integer i4 = Integer.valueOf(127)的;这就是判断i4==i6为true的原因了。接下来,关键就是看valueOf()函数了。只要看看valueOf()函数的源码就会明白了。
Paste_Image.png

Paste_Image.png

从代码上看出, 当要赋的值在[-128~127]范围内,则会直接指向该值的引用,不用去new 个对象到堆内存中去了。因为Integer已经缓存了数据。但是,当超出了数组的范围值时,就会去自动装箱在堆内存中建一个新对象。所以,对i8,i9,即使基础变量值一样,封装类对象却指向不同地址。所以判断i8==i9为false
3、对于显式的new Integer(int i),JVM将直接分配新空间。这样两个new Integer(int i)分配的堆内存空间肯定不是同一个,所以判断i10== i11为false。
此外两点, 显式的new Integer(int i)和int自动装箱成的Integer,并不会是同一个对象。他们也是两个不同的存在堆内存中的空间。所以判断i2==i3为false;显式的new Integer(int i)和i范围在[-128~127]内的直接赋值Int类型的值也不指向同一个空间。如判断i4==i7为false,i7指向常量池,而i4指向的是堆内存。

接下来是我的另一个思考:Integer作为对象包装器类,是没有set()方法的。他的值是final的。


Paste_Image.png

那我就是想给他改值怎么办。可以!用反射。

          Integer j1 = 2;
        Integer j2 = 2;
        System.out.println("j1 = j2? " + (j1 == j2));  //true
        try {
            Field field = Integer.class.getDeclaredField("value");
            field.setAccessible(true);
            field.set(j2,129);
            System.out.println( "j1+"+j1+"+j2+" + j2
                    +"\nSystem.identityHashCode(j1)"+System.identityHashCode(j1)
                    +"\nSystem.identityHashCode(j2)"+System.identityHashCode(j2)
                    +"\nj1 == j2" + (j1 == j2));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        Integer j3 = 2;
        Integer j4 = 2;
 System.out.println("j3+"+j3+"\nSystem.identityHashCode(j3)"+System.identityHashCode(j3)+"\nj3 = j4? " + (j3 == j4));

输出结果:


Paste_Image.png

我们可以看出什么呢?常量池里的对应值改变了,所有调用2的值返回的都是129。这是为什么,我画了个图,请看:

Paste_Image.png

当我通过反射修改值时,改变了就是常量池中,IntegerCache.cache[]数组中的值。而我们的下标并没有改变。(这里可能会有人说我常量池与IntegerCache.cache[]数组之间理解有问题,我还没深入研究过,只是想表示是以数组方式存储的)
所以第三步我们所谓的赋值“=”,对应的是常量池中2对应的下标里的值,改成了129。
就好比:int[10] i = {0,1,2,3,9} 变成了int[10] i = {0,1,129,3,9} 。
因此上面的输出结果没问题,所有对应的Integer里,本来是2的值都变成了129。所以!!!没事别像我一样瞎想。

String的存储
对于使用字面量赋值方式。JVM为了节省空间,会首先查找JVM中是否有对应的字符串常量。如果已经存在,则直接返回该引用,而无需重新创建对象。对象new创建方式,JVM将分配新空间。

String a="1";
        String b="1";
        int aHashCode = System.identityHashCode(a);
        int bHashCode = System.identityHashCode(b);
        System.out.print("\na:"+a+"\nb:"+b);
        System.out.print("\naHashCode:"+aHashCode+"\nbHashCode:"+bHashCode);
        try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] valueChar = (char[]) value.get(b);
        valueChar[0] = '2';
        String c="1";
        String d="2";
        int cHashCode = System.identityHashCode(c);
        int dHashCode = System.identityHashCode(d);
        System.out.print("\na:"+a+"\nb:"+b+"\nc:"+c+"\nd:"+d);
        System.out.print("\naHashCode:"+aHashCode+"\nbHashCode:"+bHashCode+"\ncHashCode:"+cHashCode+"\ndHashCode:"+dHashCode);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

对应的输出:

Paste_Image.png

一个例子,来自:http://blog.csdn.net/hejingyuan6/article/details/50489171

        String s1 = "china";
        String s2 = "china";
        String ss1 = new String("china");
        String ss2 = new String("china");
        int i = 1;
        int j = 1;
        public static final int i1 = 1;
        public static final int j1 = 1;
        Integer it1 = 127;
        Integer it2 = 127;
        Integer it11 = 128;
        Integer it12 = 128;

还有一点,就是String的拼接,作用于哪要看虚拟机和编译的jdk版本。我没深入研究,你们看着办吧。反正,对于频繁长拼接,用StringBuffer更好。

最后附上整个class的测试代码:

package com.yy007.zxing;


import java.lang.reflect.Field;

/**
 * Created by 仁昌居士 on 2017/6/16.
 * Description:
 */

public class TestAcitivity {
    /**
     * @param args
     */
    public static void main(String[] args) {
        int i1 = 128;
        Integer i2 = 128;
        Integer i3 = new Integer(128);
        //Integer会自动拆箱为int,所以为true
        System.out.println(i1 == i2);
        System.out.println(i1 == i3);
        System.out.println(i2 == i3);
        System.out.println("**************");
        Integer i4 = 127;//java在编译的时候,被翻译成-> Integer i5 = Integer.valueOf(127);
        Integer i5 = 127;
        Integer i6 = Integer.valueOf(127);
        System.out.println(i4 == i5);//true
        System.out.println(i4 == i6);//true
        Integer i7 = new Integer(127);
        System.out.println(i4 == i7); //false
        Integer i8 = 128;
        Integer i9 = 128;
        System.out.println(i8 == i9);//false
        Integer i10 = new Integer(128);
        Integer i11 = new Integer(128);
        System.out.println(i10 == i11);  //false


        System.out.println("**************");
        Integer j1 = 2;
        Integer j2 = 2;
        System.out.println("j1 = j2? " + (j1 == j2));  //true
        try {
            Field field = Integer.class.getDeclaredField("value");
            field.setAccessible(true);
            field.set(j2,129);
            System.out.println( "j1+"+j1+"+j2+" + j2
                    +"\nSystem.identityHashCode(j1)"+System.identityHashCode(j1)
                    +"\nSystem.identityHashCode(j2)"+System.identityHashCode(j2)
                    +"\nj1 == j2" + (j1 == j2));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        Integer j3 = 2;
        Integer j4 = 2;
        System.out.println("j3+"+j3+"\nSystem.identityHashCode(j3)"+System.identityHashCode(j3)+"\nj3 = j4? " + (j3 == j4));


        System.out.println("**************");
        String a="1";
        String b="1";
        int aHashCode = System.identityHashCode(a);
        int bHashCode = System.identityHashCode(b);
        System.out.print("\na:"+a+"\nb:"+b);
        System.out.print("\naHashCode:"+aHashCode+"\nbHashCode:"+bHashCode);
        try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] valueChar = (char[]) value.get(b);
        valueChar[0] = '2';
        String c="1";
        String d="2";
        int cHashCode = System.identityHashCode(c);
        int dHashCode = System.identityHashCode(d);
        System.out.print("\na:"+a+"\nb:"+b+"\nc:"+c+"\nd:"+d);
        System.out.print("\naHashCode:"+aHashCode+"\nbHashCode:"+bHashCode+"\ncHashCode:"+cHashCode+"\ndHashCode:"+dHashCode);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

推荐阅读更多精彩内容