常用類——String類
來自專欄從此JAVA不懵逼3 人贊了文章
String類型(java.lang.String)
1.字元串的創建
常量池:
Java中的常量池,實際上分為兩種形態:靜態常量池和運行時常量池。
所謂靜態常量池,即*.class文件中的常量池,class文件中的常量池不僅僅包含字元串,還包含類、方法的信息,佔用class文件絕大部分空間。
而運行時常量池,則是JVM虛擬機在完成類裝載操作後,將class文件中的常量池載入到內存中,並保存在方法區中,我們常說的常量池,就是指方法區中的運行時常量池。
String a = "Hello World";
此種創建方式下,編譯器首先檢查常量池中是否含有此字元串常量,沒有則創建,然後JVM會開闢一塊堆內存存放一個隱式對象指向該字元串常量,接著在棧空間里創建a變數,a變數指向該塊堆內存首地址。
String a = new String("Hello World");
此種創建方式下,直接在堆內存new一個對象,並在常量池中創建「Hello World」字元串,該對象指向常量池中的「Hello World」字元串,然後創建a變數,並將a變數指向該塊堆內存首地址。
2.字元串的特性
- String是一個不可變的字元序列
String a1 = new String("Hello");String a2 = new String("World");a1 += a2;
此段代碼執行時,首先在內存中分配一塊空間,它的大小為a1和a2空間大小之和,然後將a1和a2的內容複製到該內存空間相應位置,最後將變數a1指向該內存空間。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; /** Cache the hash code for the string */ private int hash; // Default to 0 ........}
從String源碼中分析可知,String類是final類,也即意味著String類不能被繼承,並且它的成員方法都默認為final方法。在Java中,被final修飾的類是不允許被繼承的,並且該類中的成員方法都默認為final方法。並且String類是通過char數組來存儲字元串。
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex); } if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex); } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value);}
分析String類中的substring方法可以發現,該方法的操作並不改變原有字元串的值,而是返回了一個新的字元串對象,這也與String類是一個不可改變的字元序列相吻合。
- 字元串的比較
「==」比較的是兩個字元串地址是否相同,而.equals()方法比較的是兩個字元串內容是否相同。
情況一:
String s1 = new String("java");String s2 = new String("java");System.out.println(s1==s2); //falseSystem.out.println(s1.equals(s2)); //true
此段代碼創建的是兩個對象,他們有不同的堆空間,因此「==」為false;但內容相同,因此.equals()為true。
情況二:
String s1 = new String("java");String s2 = s1;System.out.println(s1==s2); //trueSystem.out.println(s1.equals(s2)); //true
此段代碼中變數s1,s2指向的為同一堆空間,並且字元串內容相同,因此均為true。
情況三:
String s1 = "java";String s2 = "java";System.out.println(s1==s2); //trueSystem.out.println(s1.equals(s2)); //true
此段代碼中,字元串創建方式為隱式創建,即,將String視為基本數據類型,因此,若字元串值相同,則s1,s2指向同一對象;若不同,則指向不同對象。
情況四:
String s0="helloworld"; String s1="helloworld"; String s2="hello"+"world"; System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一個對象 System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一個對象
此段代碼中的s0和s1中的"helloworld」都是字元串常量,它們在編譯期就被確定了,所以s0==s1為true;而"hello」和"world」也都是字元串常量,當一個字元串由多個字元串常量連接而成時,它自己肯定也是字元串常量,所以s2也同樣在編譯期就被解析為一個字元串常量,所以s2也是常量池中"helloworld」的一個引用。
情況五:
String s0="helloworld"; String s1=new String("helloworld"); String s2="hello" + new String("world"); s3="aaa"; String s4=s0+s3; System.out.println( s0==s1 ); //false System.out.println( s0==s2 ); //false System.out.println( s1==s2 ); //false System.out.println( s4=="helloworldaaa" ); //false
用new String() 創建的字元串不是常量,不能在編譯期就確定,所以new String() 創建的字元串不放入常量池中,它們有自己的地址空間。
s0還是常量池中"helloworld」的引用,s1因為無法在編譯期確定,所以是運行時創建的新對象"helloworld」的引用,s2因為有後半部分new String(」world」)所以也無法在編譯期確定,所以也是一個新創建對象"helloworld」的引用。
String s4=s0+s3是運行時才可知的,而"helloworldaaa"是在編譯時就存放在常量池中的。在之前說過,String的所有函數都是返回的一個新字元串而不改變原字元串,因此+運算符會在堆中建立來兩個String對象,這兩個對象的值分別是"helloworld"和"aaa",也就是說從字元串池中複製這兩個值,接著在堆中創建兩個對象,然後再在棧空間建立變數s4,將 "helloworldaaa"的堆地址賦給s4。
情況六:
String s0 = "ab"; final String s1 = "b"; String s2 = "a" + s1; System.out.println((s0 == s2)); // true
s1字元串加了final修飾,對於final修飾的變數,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的位元組碼流中。所以此時的"a" + s1和"a" + "b"效果是一樣的。故上面程序的結果為true。
情況七:
String s0 = "ab"; final String s1 = getS1(); String s2 = "a" + s1; System.out.println("===========test10============"); System.out.println((s0 == s2)); // false private static String getS1() { return "b"; }
這裡雖然將s1用final修飾了,但是由於其賦值是通過方法調用返回的,那麼它的值只能在運行期間確定,因此s0和s2指向的不是同一個對象,故上面程序的結果為false。
3.String、StringBuffer、StringBuilder的區別
(1)可變與不可變:String是不可變字元串對象,StringBuilder和StringBuffer是可變字元串對象(其內部的字元數組長度可變)。
(2)是否多線程安全:String中的對象是不可變的,也就可以理解為常量,顯然線程安全。StringBuffer 與 StringBuilder 中的方法和功能完全是等價的,只是StringBuffer 中的方法大都採用了synchronized 關鍵字進行修飾,因此是線程安全的,而 StringBuilder 沒有這個修飾,可以被認為是非線程安全的。
4.關於String str = new String("abc")創建了多少個對象?
這個問題在很多書籍上都有說到比如《Java程序員面試寶典》,包括很多國內大公司筆試面試題都會遇到,大部分網上流傳的以及一些面試書籍上都說是2個對象,這種說法是片面的。
首先必須弄清楚創建對象的含義,創建是什麼時候創建的?這段代碼在運行期間會創建2個對象么?毫無疑問不可能,用javap -c反編譯即可得到JVM執行的位元組碼內容:
很顯然,new只調用了一次,也就是說只創建了一個對象。而這道題目讓人混淆的地方就是這裡,這段代碼在運行期間確實只創建了一個對象,即在堆上創建了"abc"對象。而為什麼大家都在說是2個對象呢,這裡面要澄清一個概念,該段代碼執行過程和類的載入過程是有區別的。在類載入的過程中,確實在運行時常量池中創建了一個"abc"對象,而在代碼執行過程中確實只創建了一個String對象。
因此,這個問題如果換成 String str = new String("abc")涉及到幾個String對象?合理的解釋是2個。
5.常用方法
http://1.int length(); 返回字元串的長度
http://2.int indexOf(int ch); 返回該字元串第一個ch字元所在的位置
http://3.int indexOf(String str); 返回該字元串子字元串str起始位置
http://4.int lastIndexOf(int ch); 返回該字元串最後一個ch字元的位置
http://5.int lastIndexOf(String str); 返回該字元串最後一個str子字元串的起始位置
6.String subString(int beginIndex); 從字元串某個位置到結束截取字元串
7. String subString(int beginIndex, int endIndex); 從字元串的某個起始位置到結束(不包括末位置)截取字元串
8.String trim(); 返回去除了前後空格的字元串
9.boolean equals( Obiect object ); 將自定字元串與指定對象比較
10.String toLowerCase(); 將字母轉換成小寫
11.String toUpperCase() 將字幕轉換成大寫
12.char charAt(int index); 獲取字元串指定位子的字元
13 .String [] split(String regex ,int limit); 將字元串進行截取,返回子字元串數組
14.byte[] getBytes(); 將字元串轉變為byte類型的數組
附:java.lang.StringAPI文檔鏈接:String (Java SE 9 & JDK 9 )
參考資料:
String (Java SE 9 & JDK 9 )
深入理解Java中的String - 平凡希 - 博客園
推薦閱讀:
※理論上最好的編程語言: 讀寫省略篇
※讀書:Python編程快速上手——讓繁瑣工作自動化2
※JAI編程語言和Jonathan Blow
※python自動化常用模塊
※簡析 Python 的 `NotImplemented`
TAG:編程語言 |