java中的字符串String的不可變性

java中的String的不可變性,網上已經有了大把大把的文章證實了。通常都是都是經過代碼證實的,固然,我也不能免俗,我先列一段代碼:java

public static void main(String[]args){
    String one=new String("abc");
    String two=new String("abc");
    System.out.println("第一步one==two:"+(one==two));
    System.out.println("第二步one.equals(two):"+one.equals(two));
    System.out.println();
    
    String three=one;
    System.out.println("第三步one==three:"+(one==three));
    System.out.println("第四步one.equals(three):"+one.equals(three));
    System.out.println();
    
    one="abcdefg";
    System.out.println("第五步one==three:"+(one==three));
    System.out.println("第六步one.equals(three):"+one.equals(three));
    System.out.println();
    
    String seven="abc";
    String eight="abc";
    System.out.println("第七步seven==eight:"+(seven==eight));
    System.out.println("第八步seven.equals(eight):"+seven.equals(eight));
}
複製代碼

答案爲:數組

第一步one==two:false
    第二步one.equals(two):true
    
    第三步one==three:true
    第四步one.equals(three):true
    
    第五步one==three:false
    第六步one.equals(three):false
    
    第七步seven==eight:true
    第八步seven.equals(eight):true
複製代碼

咱們一步一步來看代碼。spa

第一步:第一步的答案是爲false,這固然是沒有疑問的,是new出的兩個不一樣對象,經過「==」來比較的話,比較的是兩個對象之間的引用,固然是不相同的,因此爲false。code

第二步:第二步的答案是true。這是對的,雖然是兩個不一樣的對象,可是兩個對象引用指向的值都是同樣的,而經過「equals」來進行比較的話,比較的是兩個對象的引用所指向的值,因此爲true。cdn

第三步第四步:第三步和第四步的答案都爲true。在看第三步以前,咱們要看到以前有一個代碼:對象

String three=one;
複製代碼

這個代碼,意味着將one這個字符串的引用值賦給three,也就是說,one和three指向的是同一個對象,那麼第三步和第四步的值固然都爲true了。blog

第五步第六步:到了這一步以前,先看以前的代碼:three

String one=new String("abc");
    String three=one;
    one="abcdefg";
    System.out.println("第五步one==three:"+(one==three));
    System.out.println("第六步one.equals(three):"+one.equals(three));
複製代碼

我把以前的和第五步和第六步相關的代碼提煉出來。由於剛開始one和three是指向的同一個對象,可是後面one又改變了,one="abcdefg",因而得出的第五步和第六步的值都是false。這是爲何呢?字符串

若是one和three指向的都是同一個對象,那麼對one的修改應該是徹底同步到three上面纔對啊?get

其實,在one=「abcdefg」這段代碼,由於在如今的jdk版本中,String常量池的存在於堆中,當發如今常量池裏面沒有「abcdefg」這個字符串,那麼就會生成一個新的字符串對象。 那麼,咱們就能理解另外爲何第五步和第六步的值都爲false了,由於one已經其實是一個新new出來的對象,和three是徹底不一樣的兩個對象了。

這個地方,咱們引入的正是java中的String的不可變性

第七步第八步:到這一步,咱們先把代碼提煉出來。

String seven="abc";
    String eight="abc";
    System.out.println("第七步seven==eight:"+(seven==eight));
    System.out.println("第八步seven.equals(eight):"+seven.equals(eight));
複製代碼

答案都是true,咱們難免產生了迷惑,這和第五步,第六步說的彷佛不同啊?這個時候咱們深刻的去了解一下在java中,String類型的究竟是怎麼生成對象的。

先說new String("abc")的方法建立的字符串,這種方法是無論何時,都是new一個對象出來。

而另一種是String seven="abc";這種類型的,這種類型的方法呢,先在棧中建立一個對String類的對象引用變量str,而後查找棧中(也是咱們所說的字符串常量池,jdk版本爲1.6及以前,常量池是存在Pern Gen區,也就是方法區。1.7版本後常量池就存在與堆中了)有沒有存放"abc",若是沒有,則將"abc"存放進棧,並令str指向」abc」,若是已經有」abc」 則直接令str指向「abc」。 也就是說,這種方法,可能不會new一個對象出來,可能只是指向了同一個引用而已。

這樣的話,咱們便能理解第五步第六步,也能理解第七步和第八步了。seven是生成了一個新的對象,可是eight並無生成一個新的對象,它只是在棧中發現了它須要的並且是由seven生成的「abc」,因而它便直接指向了這個對象。

固然,僅僅經過咱們的代碼來驗證,仍是不夠的,一般咱們須要去了解一下String類型的源碼,才能更加深入的理解java中的String的不可變這個特性。

String類型的源碼以下:

從這一段代碼咱們能夠發現,String類型的底層實際上是char類型的數組,並且是由final修飾的。因而咱們能夠得出兩個結論:

第一:String類型的長度是不可改變的。(由於底層是數組)
    
    第二:String類型的值是不可改變的。(由於是final修飾的)
複製代碼

固然,實際上在java中由於反射的緣由,咱們能夠對String類型的值進行修改,真正能堅持不變的多是String類型的長度。

經過反射修改String類型的值的代碼以下:

static final Unsafe unsafe = getUnsafe();
    static final boolean is64bit = true;

 public static void main(String[]args) throws NoSuchFieldException, IllegalAccessException {
    String s = "Hello World";
    Double[] ascending = new Double[16];
    for(int i=0;i<ascending.length;i++){
        ascending[i] = (double) i;
    }
    System.out.println("未經過反射修改字符串的值:");
    printAddresses(s, ascending);
    //獲取String類中的value屬性
    Field valueField = String.class.getDeclaredField("value");
    //改變value屬性的訪問權限
    valueField.setAccessible(true);
    //獲取s對象上的value屬性的值
    char[] value = (char[]) valueField.get(s);
    //改變value所引用的數組中的第6個字符
    value[5] = '_';
    System.out.println("經過反射修改字符串的值:");
    printAddresses(s, ascending);
}

/**
 * 獲取對象的引用值
 * @param label
 * @param objects
 */
public static void printAddresses(String label, Object... objects) {
    System.out.print("s="+label + "  引用值爲: 0x");
    long last = 0;
    int offset = unsafe.arrayBaseOffset(objects.getClass());
    int scale = unsafe.arrayIndexScale(objects.getClass());
    switch (scale) {
        case 4:
            long factor = is64bit ? 8 : 1;
            final long i1 = (unsafe.getInt(objects, offset) & 0xFFFFFFFFL) * factor;
            System.out.print(Long.toHexString(i1));
            for (int i = 1; i < objects.length; i++) {
                final long i2 = (unsafe.getInt(objects, offset + i * 4) & 0xFFFFFFFFL) * factor;
            }
            break;
        case 8:
            throw new AssertionError("Not supported");
    }
    System.out.println();
}

private static Unsafe getUnsafe() {
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        return (Unsafe) theUnsafe.get(null);
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}
複製代碼

打印出的結果爲:

未經過反射修改字符串的值:
    s=Hello World  引用值爲: 0x76be43a18
    經過反射修改字符串的值:
    s=Hello_World  引用值爲: 0x76be43a18
複製代碼

咱們發現,對象的值變了,可是引用沒有變。

結論:

因此,實際上,String類型的不可變,是長度的不可變,它的值確實是能夠經過反射進行改變的。

相關文章
相關標籤/搜索