1. 字符串不是基本數據類型,而是一個類
Java沒有內置的字符串類型,而是在標準Java類庫中提供了一個預定義類,叫做String。每個用雙引號括起來的字符串都是String類的一個實例。
2. 字符串的存儲機制
這裏會用到Java虛擬機的知識,關於此部分不做贅述。
字符串的初始化有兩種方式: String s1 = "abc"; String s3 = new String("abc");
(1)以字面量形式創建字符串對象
(字面量:文本字符串、聲明爲final的常量值等)
String s1 = "abc"; 對象引用存放在Java虛擬機棧的局部變量表中,而字符串對象”abc“存儲在方法區中的運行時常量池中。當代碼中出現以字面量形式創建字符串對象時,JVM首先會對這個字面量進行檢查,如果字符串常量池中存在相同內容的字符串對象,判斷依據String.equals(Object obj),也是就判斷的內容是否相同,如果存在則將這個字符串的引用返。如果不存在則創建該字符串對象,然後把它加入到字符串常量池中,並返回它的引用。
String s1 = "abc"; // 1
String s2= "abc"; // 2
當執行第1條語句時,JVM檢測“abc”這個字面量,這裏我們認爲沒有內容爲“abc”的對象存在。JVM通過字符串常量池查找不到內容爲“abc”的字符串對象存在,那麼就創建這個字符串對象,然後將剛創建的對象放入到字符串常量池中,並且將引用返回給變量s1。當執行第2條語句時,JVM還是要檢測這個字面量,JVM通過查找字符串常量池,發現內容爲“abc”字符串對象存在,於是將已經存在的字符串對象的引用返回給變量s2,這裏不會重新創建新的字符串對象。
(2)new一個字符串對象
String s3 = new String("abc"); 相當於"abc" 和 new String()兩個操作。先在虛擬機棧中創建一個對String類的對象引用變量s3,然後new()操作會在Java堆中產生一個新的對象"abc",並將s3指向堆中的"abc",同時檢查字符串常量池中是否有對象"abc",如果沒有則產生一個字符串常量"abc",並將它添加到字符串常量池中;如果有則不產生。
new String("abc")創建了幾個對象?
答案:一個或兩個。如果常量池中原來有"abc",那麼只需要在Java堆中創建新的對象;如果常量池中沒有,那麼除了在Java堆中創建對象,還需在字符串常量池中創建”abc“.
String s1 = "abc"; //在字符串常量池中創建一個“abc”字符串對象
String s2 = "abc"; //“abc”已經在常量池中存在,只需要將引用s2指向“abc”
String s3 = new String("abc"); //在堆中創建新對象
String s4 = new String("abc"); //在堆中又創建一個新對象
對應的以上代碼存儲位置爲
3. 字符串的"=="、equals 和 hashCode的區別
(1)”==“運算符用來比較兩個變量的值是否相等。
如果兩個變量是基本數據類型,可以直接使用”==“來比較其對應的值是否相等。
如果變量是引用類型,使用”==“只能判斷兩個變量是否指向同一塊存儲空間。
想要比較這兩個對象的內容是否相等,就需要使用equals方法。
(2)equals方法
equals方法是Object類提供的方法之一。Object類中定義的equals(Object)方法是直接使用”==“運算符比較的兩個對象,所以在沒有覆蓋equals(Object)方法的情況下,equals(Object)與”==“一樣,比較的是引用。
在字符串中,覆蓋了Object的equals方法,比較是兩個對象的內容是否相等。
我們來看下面這一段代碼:
public class StringTest{
public static void main(String[] args) {
String s1 = "abc"; //在字符串常量池中創建一個“abc”字符串對象
String s2 = "abc"; //“abc”已經在常量池中存在,只需要將引用s2指向“abc”
String s3 = new String("abc"); //在堆中創建新對象
String s4 = new String("abc"); //在堆中又創建一個新對象
System.out.println(s1==s2); //true
System.out.println(s1==s3); //false
System.out.println(s3==s4); //false
System.out.println(s1.equals(s2)); //true
System.out.println(s1.equals(s3)); //true
System.out.println(s3.equals(s4)); //true
}
}
運行結果
true
false
false
true
true
true
s1和s2都指向字符串常量池中abc,因此這兩個變量所對應內存中的數值(即地址)相等,“s1==s2”返回true;
而s3指向堆中的new String(“abc”),因此返回false; 而s4指向的是堆中另一個new String(“abc”),“s1==s3”和"s3==s4"都返回fasle
equals比較的是字符串內容是否相同,所有後面三個都返回true.
再添幾行代碼:
String s5 = s1;
String s6 = s1 + "";
String s7 = "ab"+"c";
System.out.println(s1==s5); //true
System.out.println(s1==s6); //false
System.out.println(s1==s7); //true
運行結果:
true
false
true
語句“String s5 = s1;”直接將s1的引用賦給s5; 所以“s1==s5”返回true;
而語句“String s6 = s1 +"" ”;採用拼接的方式將變量s1和空字符串拼接起來,雖然沒有看到new關鍵詞,但是由於String類“+”操作符被重載之後,重載的方法之中一定會重新new一個String,所以s6存儲在堆中。 “s1==s6”返回false;
語句“String s7 = "ab" +"c" ”;採用拼接的方式將常量“ab”和常量“c”拼接起來,在編譯器就轉換爲“abc”,由於常量池中已經存在“abc”,所以直接將引用賦給s7。 “s1==s7”返回true;
此外,字符串還提供了intern()方法,String的intern()方法是一個本地方法,定義爲public native String intern(); intern()方法的價值在於讓開發者能將注意力集中到String池上。當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(該對象由 equals(Object) 方法確定),則返回池中的字符串。否則,將此 String 對象添加到池中,並且返回此 String 對象的引用。
再添加代碼
System.out.println(s3.intern() == s1); //true
System.out.println(s3.intern() == s4.intern()); //true
運行結果:
true
true
s3.intern( ) 和 s4.intern( ) 都指向字符串常量池中的“abc”,所以都返回true.
(3) hashCode()方法
hashCode()方法也是從Object類中繼承過來的,Object類中的hashCode()方法返回對象在內存中地址轉換成的一個int值,所以如果沒有重寫hashCode()方法,任何對象的hashCode()方法都是不相等的。
一般來講,equals()方法是給用戶調用的,如果需要判斷兩個對象是否相等,可以重寫equals()方法,然後在代碼中調用。對於hashCode()方法用戶一般不去調用它,例如在hashmap中,由於key是不可以重複的,它在判斷key是否重複時就判斷了hashCode()方法,而且也用到了equals()方法。此處“不可重複”指的是equals()和hashCode()只要有一個不等就可以了。
一般覆蓋equals()方法的同時也要覆蓋hashCode()方法,否則,會違反Object.hashCode的通用約定。
x.equals(y)返回true ——> x.hashCode() 等於 y.hashCode();
x.equals(y)返回false ——> x.hashCode() 有可能等於 y.hashCode(); 也有可能不等。
x.hashCode() 不等於 y.hashCode(); ——> x.equals(y)一定返回false;
x.hashCode() 等於 y.hashCode(); ——> x.equals(y)可能返回true,也可能返回false。
4. 字符串的常用方法
(1) 創建並初始化字符串:
1). 使用字符串常量直接初始化 String s="hello!";
2). 使用構造方法創建並初始化
new String();//初始化一個對象,表示空字符序列
new String(value);//利用已存在的字符串常量創建一個新的對象
new String (char[] value);//利用一個字符數組創建一個字符串
new String(char[] value,int offset,int count);//截取字符數組offset到count的字符創建一個非空串
new String(StringBuffer buffer);//利用StringBuffer對象初始化String對象
(2) 獲取字符串信息:
下標:int indexOf(String str); int lastIndexOf(String str);
字符:char charAt(int index)
字節數組:byte[ ] getBytes()
長度:int length()
(3)判斷字符串
相等:bool equals(String str)
前綴:startsWith(前綴)
後綴:endsWith(後綴)
大小:int compareTo()
(4)替換字符串
去掉前後空格:String trim()
替換:String replace(CharSequence oldString, CharSequence newString)
(5)截取字符串
單點截取:subString(int beginIndex)
雙點截取:subString(int beginIndex,int endIndex)
(6)轉換
轉換爲字符數組:char[ ] toCharArray()
分割爲字符串數組:String[] split(分隔符)
基本數據型態轉換爲字符串: String.valueOf();
5. 空串與null串
(1)空串是一個Java對象,是長度爲0的字符串,有自己的串長度(0)和內容(空)。
判斷一個字符串是否爲空:
if(str.length() == 0) 或 if(str.equals(“”))
(2)String s = null; 表示目前沒有任何對象與變量s關聯。
判斷一個字符串是否爲空:
if(str == null)
檢查一個字符串既不是null也不是空串: if(str != null && str.length() != 0)