java 經常使用類-String-1

1、字符串相關的類

1.1 String 的特性

  1. String類:表明字符串。Java 程序中的全部字符串字面值(如 "abc" )都做爲此類的實例實現。
  2. String是一個final類,表明不可變的字符序列。
    • final修飾的類不能被繼承
  3. 字符串是常量,用雙引號引發來表示。它們的值在建立以後不能更改。
  4. String對象的字符內容是存儲在一個字符數組value[]中的。
    • 數據存儲結構中,有鏈式存儲結構和順序存儲結構兩種。顯然String底層選擇了char型數組類型的順序存儲結構。

1.2 String部分源碼

Windows電腦,idea中。雙擊shift,輸入String,便可出來。java

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
    ....
}

從這部分源碼中,咱們就能夠很好的理解「1.1 String 的特性」中的內容了!!!字符串,字符串,就是字符構建起來的串。面試

1.3 String再次理解

1.3.1 上代碼
public class StringTest {
    @Test
    public void test1() {
        // String name = "abc"; 字面量的定義方式
        String name = "abc";
        String des = "abc";
        name = "hell world";
        System.out.println("name:"+name);// hello world
        System.out.println("des:"+des);// abc
    }

    @Test
    public void test2() {
        final int a = 2;
        a = 3; // 編譯不經過
    }
}
  1. 第一個問題:爲何name = "hell world",編譯成功,並能運行,不是說「它們的值在建立以後不能更改」嗎?;而test2()中卻編譯失敗?數組

    • 首先,String屬性引用類型,而int是基本類型。ide

    • 引用類型變量存儲地址值,而a是一個指向int類型的引用,指向2這個字面值。所以final int 修飾的變量就變爲常量了,常量是不能修改其值的,因此test2()編譯失敗。oop

    • 那麼怎麼理解String 底層存儲使用final修飾的char型數組,更改其值爲何編譯成功,並能運行?簡單的JVM走一波。圖一:this

      從圖中咱們能夠看出:idea

      • 常量池當中,是不會存儲兩個相同的字符串的。
      • name、des存儲的是地址值。name和des都指向0x8888這個地址

      圖二:code

      從圖中咱們能夠看出:對象

      • 這就很好解釋了爲何輸出結果是name爲hello world 而des依然爲abc。
      • 同時也解釋了「它們的值在建立以後不能更改」,原始的值「abc」的確沒有被修改,而是在字符串常量池中從新建立了一份。
      • 這也能夠看出頻繁的對字符串進行增刪改操做的話,很耗費內存資源。
1.3.2 初步總結
  1. String:字符串,使用一對""引發來表示。
  2. String聲明爲final的,不可被繼承。
  3. String實現了Serializable接口:表示字符串是支持序列化的。
    • 實現了Comparable接口:表示String能夠比較大小
  4. String內部定義了final char[] value用於存儲字符串數據
  5. String:表明不可變的字符序列。簡稱:不可變性。
    • 當對字符串從新賦值時,須要重寫指定內存區域賦值,不能使用原有的value進行賦值。
    • 當對現有的字符串進行鏈接操做時,也須要從新指定內存區域賦值,不能使用原有的value進行賦值。
    • 當調用String的replace()方法修改指定字符或字符串時,也須要從新指定內存區域賦值,不能使用原有的value進行賦值。
  6. 經過字面量的方式(區別於new)給一個字符串賦值,此時的字符串值聲明在字符串常量池中。
  7. 字符串常量池中是不會存儲相同內容的字符串的。

1.4 判斷對錯

@Test
    public void test3() {
        String s1="javaWeb";
        String s2="javaWeb";

        String s3 = new String("javaWeb");
        String s4 = new String("javaWeb");

        System.out.println(s1==s2);
        System.out.println(s1==s3);
        System.out.println(s1==s4);
        System.out.println(s3==s4);
    }

直觀判斷:blog

  1. s1==s2。在字符串常量池中相同的字符串只會存儲一份,s1和s2存儲相同的地址。因此爲true
  2. s1==s3。他們分別指向不一樣地方,因此爲false
  3. s1==s4。他們分別指向不一樣地方,因此爲false
  4. s3==s4。只要是new便會在堆空間中開闢一份空間,JVM纔不會管你new的內容是否和前面的相同。因此爲false

集合JVM判斷:

結合JVM圖形就能夠很清楚的明白到底爲何錯,爲何對了。

1.5 判斷對象的屬性

public class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

}
@Test
    public void test4() {
        Person p1 = new Person("Tom", 12);
        Person p2 = new Person("Tom", 12);
        System.out.println(p1.name==p2.name);// true?false?
    }

顯然爲true,Tom存儲在字符串常量池中,有且僅有一份。故p1.name==p2.name必然爲true

再來:

@Test
    public void test4() {
        Person p1 = new Person("Tom", 12);
        Person p2 = new Person("Tom", 12);
        System.out.println(p1.name==p2.name);

        p2.name="cxk";
        System.out.println(p1.name==p2.name);// true?false
    }

確定是false, p2.name="cxk";會先在字符串常量池中查找,沒有就在常量池中建一個, p2.name地址指向它便可。【字符串的不可變性】

1.6 拓展:

  1. String s = new String("abc");方式建立對象,在內存中建立了幾個對象?

    答:2個。一個是堆空間中new結構,另外一個是char[ ]對應的常量池中的數據:」abc「;

  2. 來,搞一下這個。

    public void test3(){
            String s1 = "javaEE";
            String s2 = "hadoop";
    
            String s3 = "javaEEhadoop";
            String s4 = "javaEE" + "hadoop";
            String s5 = s1 + "hadoop";
            String s6 = "javaEE" + s2;
            String s7 = s1 + s2;
    
            System.out.println(s3 == s4);
            System.out.println(s3 == s5);
            System.out.println(s3 == s6);
            System.out.println(s3 == s7);
            System.out.println(s5 == s6);
            System.out.println(s5 == s7);
            System.out.println(s6 == s7);
        
            String s8 = s6.intern();
            System.out.println(s3 == s8);
        }
    • s3 == s4爲true。字面量或者字面量之間的鏈接,指向同一個對象,在常量池中聲明。
    • s五、s六、s7都是字面量拼接變量或者變量拼接變量,他們不是在常量池中聲明而是在堆中聲明。能夠把它想象爲new,從而儘管內容相同,可是地址值倒是是不相同的。

    結論:

    • 常量與常量的拼接結果在常量池。且常量池中不會存在相同內容的常量。  只要其中有一個是變量,結果就在堆中
    • 若是拼接的結果調用intern()方法,返回值就在常量池中

1.7 面試題

代碼一:

public class StringTest2 {
    String str = new String("good");
    char[] ch = {'t', 'e', 's', 't'};

    public void change(String str, char ch[]) {
        str = "test ok";
        System.out.println("======"+str);
        ch[0] = 'b';
    }

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

輸出結果是多少?

解析:作這道題必須明白幾個知識點

  1. java中方法參數的傳遞機制——值傳遞。
    • 值傳遞。即將實際參數的副本(複製品)傳入到方法內,而參數自己不受影響。
      • 形參是基本數據類型:將實參基本數據類型變量的「數據值」傳遞給形參【原始值不受影響】
      • 形參是引用數據類型:將實參引用數據類型變量的「地址值」傳遞給形參【由於傳遞副本爲地址值,因此某些原始值會受影響,某些不會,如String類型具備不可變性】
  2. String類型具備不可變性。

分析:

代碼二:

public class StringTest2 {
    String str = new String("good");
    char[] ch = {'t', 'e', 's', 't'};

    public void change(String str, char ch[]) {
        // 第二步:
//對於方法中的str,因爲第一步的賦值操做,它的地址值和「String str = new String("good");」中的str同樣,內容都爲good。
//接下來方法中對它進行賦值操做「test ok」。
//因爲String類型的不可變性(看源碼類爲final修飾,數據存儲結構爲也爲final修飾的char型數組),不可修改。
//所以JVM執行'str ="test ok";'時會在字符串常量池中新建一個「test ok」而且更新方法中str的地址值。 
//至此兩個str的地址值不一樣了,指向的內容也不一樣了。 能夠看「代碼三」中的對比結果。
        str = "test ok";
       
        // 第二步;
//對於方法中的ch,因爲第一步的賦值操做,它的地址值和「char[] ch = {'t', 'e', 's', 't'};」中的ch同樣,內容都爲test。
//接下來,在方法中執行"ch[0] = 'b';" 數組中的第一個元素被賦值爲b。因爲是引用類型,此時類中成員變量char[] ch = {'t', 'e', 's', 't'};值也變爲best。
        ch[0] = 'b';
    }

    public static void main(String[] args) {
        StringTest2 ex = new StringTest2();
        // 第一步
 // 傳遞的兩個實參,參數一,其實是把StringTest2類中成員變量str的地址值複製一份給StringTest2類中change方法中的形參變量str
//  同理,StringTest2類中成員變量ch(數組:也是引用類型)的地址值複製一份給StringTest2類中change方法中的形參變量ch[]
        ex.change(ex.str, ex.ch);
        System.out.print(ex.str + " and ");
        System.out.println(ex.ch);
    }
}

代碼三:

public class StringTest2 {
    String str = new String("good");
    char[] ch = {'t', 'e', 's', 't'};

    public void change(String str, char ch[]) {
        System.out.print("賦值以前兩個str的地址值比較爲:");
        System.out.println(this.str==str);
        str = "test ok";
        System.out.print("賦值以後兩個str的地址值比較爲:");
        System.out.println(this.str==str);
        System.out.println("====================分割線========================");
        ch[0] = 'b';
    }

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

結果:

相關文章
相關標籤/搜索