java中String類型變量的賦值問題

第一節 String類型的方法參數html

運行下面這段代碼,其結果是什麼?java

package com.test;

public class Example {
    
    String str = new String("good");
    char[] ch = { 'a', 'b', 'c' };

    public static void main(String[] args) {
        Example ex = new Example();
        ex.change(ex.str, ex.ch);
        System.out.println(ex.str);
        System.out.println(ex.ch);
    }

    public void change(String str, char ch[]) {
        str = "test ok";
        ch[0] = 'g';
    }
    
}

結果以下:程序員

good
gbc

解說:java 中String是 immutable的,也就是不可變,一旦初始化,引用指向的內容是不可變的(注意:是內容不可變)。面試

  也就是說,假設代碼中有String str = 「aa」;str=「bb」;,則第二條語句不是改變「aa」原來所在存儲地址中的內容,而是另外開闢了一個空間用來存儲「bb」;同時因爲str原來指向的「aa」如今已經不可達,jvm會經過GC自動回收。
 
  在方法調用時,String類型和數組屬於引用傳遞,在上述代碼中,str做爲參數傳進change(String str, char ch[]) 方法,方法參數str指向了類中str指向的字符串,但str= "test ok"; 語句使得方法參數str指向了新分配的地址,該地址存儲「test ok」,而原來的str仍然指向「good」。對於數組而言, 在change方法中,方法參數ch指向了類中ch指向的數組,ch[0] = 'g';語句改變了類中ch指向的數組的內容

 

咱們再來看下面這段代碼,它的運行結果是什麼?數組

package com.test;

public class Example {
    
    String str = new String("good");
    char[] ch = { 'a', 'b', 'c' };

    public static void main(String[] args) {
        Example ex = new Example();
        ex.change(ex.str, ex.ch);
        System.out.println(ex.str);
        System.out.println(ex.ch);
    }

    public void change(String str, char ch[]) {
        str = str.toUpperCase();
        ch = new char[]{ 'm', 'n' };
    }
    
}

結果以下:緩存

good
abc

結合前面的解釋進行理解,這個結果是否是在乎料之中?!安全

 

根據JDK中java.lang.String的源碼進行分析,從中能夠得出String類型的對象不可變的緣由,大體上有以下兩個:服務器

  一、java.lang.String類型在實現時,其內部成員變量所有使用final來修飾,保證成員變量的引用值只能經過構造函數來修改;多線程

  二、java.lang.String類型在實現時,在外部可能修改其內部存儲值的函數實現中,返回時一概構造新的String對象或者新的byte數組或者char數組;app

僅憑第1點還不能保證其不可變特性:假如經過String類型的toCharArray方法能夠直接訪問String類型內部定義的char數組,那麼即使String類型內部的char數組使用了final來修飾,也僅僅保證這個成員變量的引用不可變,而沒法保證引用指向的內存區域不可變。

第2點保證了外部不可能修改java.lang.String類型對象的內部屬性,從而保證String對象是不可變的。


 

第二節 String類型變量的賦值

2.1 String變量賦值方式:s2=new String(s1)

下面這段代碼的運行結果是什麼

package com.soft;

public class ExecutorsDemo {
    
    public static void main(String[] args) {
        String s1="abc"+"def";
        String s2=new String(s1);         if(s1.equals(s2))
            System.out.println("equals succeeded");
        if(s1==s2)
            System.out.println("==succeeded");
    }
}

結果:

equals succeeded

解說:上述代碼中,s1與s2指向不一樣的對象,可是兩個對象的內容倒是同樣的,故「s1==s2」爲假,s1.equals(s2)爲真。

此處咱們來細說一下"=="與equals的做用:

  (1)"=="操做符的做用

    A、用於基本數據類型的比較

    B、判斷引用是否指向堆內存的同一塊地址

  (2)equals的做用

    用於判斷兩個變量是不是對同一個對象的引用,即堆中的內容是否相同,返回值爲布爾類型

 

2.2 String變量賦值方式:s2 = s1

package com.soft;

public class ExecutorsDemo {
    
    public static void main(String[] args) {
        String s1 = new String("java");
        String s2 = s1;

        System.out.println(s1==s2);
        System.out.println(s1.equals(s2));
    }
}

 結果:

true
true

解說:若是理解了前面那個例子的運行狀況,那麼這個就是一目瞭然的事情,此處s1與s2指向同一個對象,"=="操做符的做用之一就是判斷引用是否指向堆內存的同一塊地址,equals的做用是判斷兩個變量是不是對同一個對象的引用(即堆中的內容是否相同),故此處均輸出「true」


 

第三節 將字符數組或字符串數組轉換爲字符串

此處再補充兩個應用場景

1、將字符數組轉換爲字符串

下面代碼中的兩種方式都可直接將字符數組轉換爲字符串,不須要遍歷拼接

package com.test;

public class Main {
    
    public Main() {
    }

    public static void main(String[] args) {
        char[] data = {'a', 'b', 'c'};
//      String str = new String(data);
        String str = String.valueOf(data);
        System.out.println(str);
    }
    
}

此處能夠看一下其餘做者的文章以深刻理解:【Java】數組不能經過toString方法轉爲字符串  http://www.cnblogs.com/ningvsban/p/3955483.html

 

2、將字符串數組轉換爲字符串

下面的代碼是咱們經常使用的方式,循環拼接

package com.test;

public class Main {
    
    public Main() {
    }

    public static void main(String[] args) {
        String[] ary = {"abc", "123", "45"};
        String s = "";
        for(String temp : ary) {
            s=s.concat(temp);//和下面的一行二選一便可
//          s += temp;
        }
        System.out.println(s);
    }
    
}

上述代碼段不須要過多解釋了


 

 

第四節 StringBuffer和StringBuilder

提到String,就不得不提一下JDK中另外兩個經常使用來表示字符串的類,StringBuffer和StringBuilder。在編寫java代碼的過程當中有時要頻繁地對字符串進行拼接,若是直接用「+」拼接的話會創建不少的String型對象,嚴重的話會對服務器資源和性能形成不小的影響;而使用StringBuilder和StringBuffer能解決以上問題。根據註釋,StringBuffer可謂老資格了,從JDK1.0時即伴隨Java征戰世界,而StringBuilder直到JDK1.5時纔出現。面試時,StringBuffer和StringBuilder的區別也是常問的話題,StringBuffer是線程安全的,而StringBuilder不是線程安全的。

1、StringBuffer和StringBuilder的共同點:

一、用來完成字符串拼接操做;

二、都是可變對象,對象內的字符緩存會隨着拼接操做而動態擴展;

三、構造時傳入內部緩存大小時,能夠下降緩存擴展的次數,明顯提高字符串拼接操做的效率;

2、StringBuffer和StringBuilder的區別:

一、StringBuilder的方法都是線程不安全的,從另一個角度講,StringBuilder類型的對象在作字符串拼接操做時,因爲少了線程同步的操做,執行效率上有很大提高;

二、StringBuffer的方法都加上了synchronized關鍵字,於是在必定的場景下,StringBuffer類型的對象都是線程安全的,但在執行效率上,因爲多了線程同步的操做,於是會有少量的損失;

在大多數場景下,字符串拼接操做都是不須要考慮多線程環境下對結果的影響的,於是使用StringBuilder類型能夠提高代碼的執行效率。

在多個線程的代碼中共享同一個StringBuffer類型的對象時,須要關注synchronized關鍵字對最終結果的影響。因爲StringBuffer類的實現中,僅僅對每一個方法使用了synchronized修飾,這隻能保證在多線程場景下,訪問StringBuffer對象的同一個方法時能夠保證最終結果的一致性,假如一個線程訪問A方法,另一個線程方法B方法,則因爲加鎖對象的不一樣,可能會出現不一致的現象,這是須要程序員特別要注意的地方。相似的,能夠參考Vector的實現和應用場景。

 

針對上面的將字符串數組轉換爲字符串,能夠藉助上面提到的StringBuilder(固然StringBuffer也能夠),代碼以下:

package com.test;

public class Main {
    
    public Main() {
    }

    public static void main(String[] args) {
        String[] ary = {"abc", "123", "45"};
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < ary.length; i++){
            sb. append(ary[i]);
        }
        String newStr = sb.toString();
        System.out.println(newStr);
    }
    
}

 

參考資料

這裏有兩篇文章,值得一讀:

(1)三分鐘理解Java中字符串(String)的存儲和賦值原理 http://blog.csdn.net/zhuiwenwen/article/details/12351565

(2)Java以內存分析和String對象 http://www.cnblogs.com/devinzhang/archive/2012/01/25/2329463.html

相關文章
相關標籤/搜索