String是一個字符串類型的類,使用“"”定義的內容都是字符串,我們需要從類的角度和內存關係上分析這個類的作用。
1.String類對象的兩種實例化方法
1.1直接賦值的方式實例化
public class Hello{
public static void main(String args[]){
String str = "Hello World!!!";
System.out.println(str);
}
}
以上是字符串對象的直接賦值,該代碼並沒有使用關鍵字new 進行,String類中其實也定義了一個構造方法
- 構造方法:public String(String str),在構造裏面依然要接收一個本類對象
- 類的實例化語法:類名稱 對象名稱 = new 類名稱( )
1.2 使用關鍵字new實例化
public class Hello{
public static void main(String args[]){
String str = new String("Hello World!!!");
System.out.println(str);
}
}
2.字符串的比較
int類型的數據可以使用“==”進行大小的比較,字符串同樣也可以.
public class Hello{
public static void main(String args[]){
String stra = "nihao";
String strb = new String("nihao");
String strc = strb;
System.out.println(stra == strb); //false
System.out.println(stra == strc); //false
System.out.println(strc == strb); //true
}
}
以上三個String類對象的內容可是完全一樣的 ,得到的結果卻不都是TRUE,下面從內存的角度分析:
可以看出字符串如果用“==”進行比較的話,其實比較的是內存的地址,地址相同則相同,不同則不同。如果需要計較字符串的話需要用到String類中的比較方法:
- 比較內容,與原始有一些差別:public boolean equals(String str)
public class Hello{
public static void main(String args[]){
String str1 = "nihao";
String str2 = new String("nihao");
String str3 = str2;
System.out.println(str1.equals(str2)); //true
System.out.println(str1.equals(str3)); //true
System.out.println(str3.equals(str2)); //true
}
}
所以,以後在開發的過程中,如果要進行字符串內容是否相等的比較,一定不要使用“==”,而是要使用stra.equals(strb)的這種方法。
**面試題:**請解釋在字符串相等判斷中“==”與equals()的區別?
- ==是Java提供的關係運算符,主要的功能是進行數值相等判斷,如果用在地址數值的比較,比較的是兩個字符串地址的數值。而不是字符串內容本身。
- equals()是String類提供的一個專門用於比較字符串內容的方法。
3. 字符串常量就是String的匿名對象
實際上任何語言都沒有字符串的概念,很多語言裏面使用的是字符串數組,Java裏面同樣也沒有字符串的概念。但是字符串是一個變成離不開的東西,Java自己創造了字符串的概念,但是此概念不是基本數據類型,他是將字符串作爲了String類的匿名對象的形式存在的。
public class Hello{
public static void main(String[] args){
String str = "hello";
System.out.println("hello".equals(str)); //true
}
}
從上面的代碼中可以看出,一個用引號括起來的字符串是可以直接調用String類中的方法的,從而證實了字符串常量其實就是一個String類的匿名對象,另外字符串字節複製的方法實際上就是相當於給一個字符串匿名對象給名字的過程。區別在於,String類的匿名對象不是用戶創建的,而是系統自動生成的。
小小技巧:爲了避免空指向異常(使用了未實例化的對象,使用了未實例化的引用數據類型)的出現,可以將字符串寫在前面調用方法。
public class Hello{
public static void main(String[] args){
String str = null;
System.out.println(str.equals("hello"));
}
}
//輸出結果
Exception in thread "main" java.lang.NullPointerException
at Hello.main(Hello.java:4)
可以看出,如果用一個空的字符串去調用String類的方法的話會產生空指向異常,但是如果用下面的方法就不會出現錯誤。因爲equals處理了空的情況
public class Hello{
public static void main(String[] args){
String str = null;
System.out.println("hello".equals(str)); //false
}
}
也就是說,在實際開發的過程中,如果需要對用戶輸入的數據跟一個字符串數據進行比較的話,請將字符串放在用戶輸入的內容前面。
4. 兩種實例化方式的區別
4.1 分析直接賦值
直接賦值的實例化方法其實就是給一個匿名對上附一個名字。
// String 字符串名 = new String(字符串值)
String str = "hello";
這樣寫會在內存中開闢一塊棧內存,一塊堆內存。
public class Hello{
public static void main(String[] args){
String stra = "hello";
String strb = "hello";
String strc = "hello";
String strd = "world";
System.out.println(stra.equals(strb)); //true
System.out.println(stra.equals(strc)); //true
System.out.println(strb.equals(strc)); //true
System.out.println(stra.equals(strd)); //false
}
}
通過上面的代碼我們發現,給一個匿名對象賦值給多個不同的名字,這些名字所指向的堆內存的地址是一樣的。也就是說,採用直接賦值的String類對象的內存地址完全相同。stra, strb, strc指向的內存地址是相同的
共享設計模式:
在JVM的底層實際上會有一個對象池,(不一定只保存String對象),當代碼之中使用了直接賦值的方式定義了一個String類對象的時候,會將此字符串對象所使用的匿名對象入池保存,此後如果還有String類對象採用直接賦值的方式,並且設置了同樣內容的時候,那麼將不會開闢新的堆內存空間,而是使用已有的對象進行引用的分配,從而繼續使用。
4.2 採用構造方法實例化
構造方法如果要使用,一定要用關鍵詞new,一旦使用了關鍵詞new,就意味着開闢一塊堆內存。
String str = new String("hello");
這種方法賦值的內存分配如下:
語句是從右往左以此執行的,最右邊是一個String的匿名對象,因此首先開闢一塊堆內存,遇到關鍵字new 的時候又會開闢一個新的堆內存空間,裏面存的也是hello,但是棧內存str保存的是new關鍵字開闢的堆內存,所以匿名對象開闢的堆內存就成爲了垃圾。因此用構造方法實例化String對象的方式是不好的。
public class Hello{
public static void main(String[] args){
String str = new String("hello");
String stra = "hello";
System.out.println(str == stra); //false
}
}
從輸出結果可以看出,由構造方法實例化的String對象是不會進入到對象池中,因爲使用了關鍵詞new。如果希望new開闢的對象也入池的話,可以使用手工的方法。public String intern();
public class Hello{
public static void main(String[] args){
String str = new String("hello").intern();
String stra = "hello";
System.out.println(str == stra); //true
}
}
4.3 面試題:請解釋String類對象的兩種實例化方法的區別:
- 直接賦值(String str = “字符串”;):只會開闢一塊堆內存,而且實例化的對象會被放入對象池中,以待下次使用。
- 構造方法賦值(String str = new String(“字符串”)):會開闢兩塊堆內存,其中匿名對象開闢的堆內存會被當做垃圾,不能被自動放入對象池中,需要用intern()方法手工入池。
5. 字符串一旦定義不會再改變。
public class Hello{
public static void main(String[] args){
String str = "hello ";
str += "world";
str += "!!!";
System.out.println(str);
}
}
通過上面的操作,我們改變了字符串str的值,內存分析如此下:
可以發現上面的代碼其實不是改變了“hello”的堆內存,而是在一步步str 的堆內存指向,同時此種方法還產生了很多的垃圾。
6.總結
String類的特點:
- String類對象的相等判斷使用equals方法完成,"=="使用的是地址數值比較
- 字符串內容一旦聲明則不可改變,String類對象內容的改變是依靠引用關係的改變而實現的。
- String類有兩種實例化方法:直接賦值可產生一塊堆內存,且對象能直接入池。不要使用構造方法實例化。