String類的equals方法和==方法的比較
先看下面的例子:
01: public class StringExample
02: {
03: public static void main (String args[])
04: {
05: String s0 = "Programming";
06: String s1 = new String ("Programming");
07: String s2 = "Program" + "ming";
08:
09: System.out.println("s0.equals(s1): " + (s0.equals(s1)));
10: System.out.println("s0.equals(s2): " + (s0.equals(s2)));
11: System.out.println("s0 == s1: " + (s0 == s1));
12: System.out.println("s0 == s2: " + (s0 == s2));
13: }}
這個例子包含了3 個String 型變量,其中兩個被賦值以常量表達式“Programming”;另一個被賦值以一個新建的值為“Programming”的String 類的實例。使用equals(...)方法和“= =”運算符進行比較產生了下列結果:
s0。equals(s1): true
s0。equals(s2): true
s0 == s1: false
s0 == s2: true
String.equals()方法比較的是字符串的內容,使用equals(...)方法會對字符串中的所有字符一個接一個地進行比較,如果完全相等那么返回true。 在這種情況下全部字符串都是相同的,所以當字符串s0 與s1 或s2 比較時我們得到的返回值均為true 。運算符“==”比較的是String 實例的引用。在這種情況下很明顯s0 和s1 并不是同一個String 實例,但s0 和s2 是同一個。讀者也許會問s0 和s2 怎么是同一個對象呢?
這個問題的答案來自于Java語言規范中關于字符串常量String Literals 的章節。本例中“Programming” ,“Program”和“ming”都是字符串常量!!它們在編譯期就被確定了當一個字符串由多個字符串常量連接而成時,例如s2 ,它同樣在編譯期就被確定為一個字符串常量。Java 確保一個字符串常量只有一份拷貝,所以當Programming”和“Program”+“ming”被確定為值相等時,Java 會設置兩個變量的引用為同一個常量的引用。在常量池constant pool 中,Java 會跟蹤所有的字符串常量。
常量池指的是在編譯期被確定,并被保存在已編譯的.class 文件中的一些數據。它包含了關于方法,類,接口等等,當然還有字符串常量的信息。當JVM 裝載了這個.class 文件。變量s0 和s2 被確定,JVM 執行了一項名為常量池解析constant pool resolution 的操作。該項操作針對字符串的處理過程包括下列3 個步驟,摘自JVM 規范5。4 節::
1 如果另一個常量池入口constant pool entry 被標記為CONSTANT_String2 ,并且指出同樣的Unicode 字符序列已經被確定,那么這項操作的結果就是為之前的常量
池入口創建的String 實例的引用。
2 否則,如果intern()方法已經被這個常量池描述的一個包含同樣Unicode 字符序列的String 實例調用過了,那么這項操作的結果就是那個相同String 實例的引用。
3 否則,一個新的String 實例會被創建它包含了CONSTANT_String 入口描述的Unicode 字符;序列這個String 實例就是該項操作的結果。
也就是說,當常量池第一次確定一個字符串,在Java 內存棧中就創建一個String 實例。在常量池中,后來的所有針對同樣字符串內容的引用,都會得到之前創建的String 實例。當JVM 處理到第6 行時,它創建了字符串常量Programming 的一份拷貝到另一個String 實例中。所以對s0 和s1 的引用的比較結果是false ,因為它們不是同一個對象。這就是為何s0==s1 的操作在某些情況下與s0.equals(s1)不同。s0==s1 比較的是對象引用的值;而s0.equals(s1)實際上執行的是字符串內容的比較。
存在于.class 文件中的常量池,在運行期被JVM 裝載,并且可以擴充。此前提到的intern()方法針對String 實例的這個意圖提供服務。當針對一個String 實例調用了intern()方法,intern()方法遵守前面概括的第3 步以外的常量池解析規則:因為實例已經存在,而不需要另外創建一個新的。所以已存在的實例的引用被加入到該常量池。來看看另一個例子:
01: import java.io.*;
02:
03: public class StringExample2
04: {
05: public static void main (String args[])
06:{
07: String sFileName = "test.txt";
08: String s0 = readStringFromFile(sFileName);
09: String s1 = readStringFromFile(sFileName);
10:
11: System.out.println("s0 == s1: " + (s0 == s1));
12: System.out.println("s0.equals(s1): " + (s0.equals(s1)));
13:
14: s0.intern();
15: s1.intern();
16:
17: System.out.println("s0 == s1: " + (s0 == s1));
18: System.out.println("s0 == s1.intern(): " +
19: (s0 == s1.intern()));
20: }
21:
22: private static String readStringFromFile (String sFileName)
23: {
24: //…read string from file…
25: }
26: }
這個例子沒有設置s0 和s1 的值為字符串常量,取而代之的是在運行期它從一個文件中讀取字符串,并把值分配給readStringFromFile(...)方法創建的String 實例。從第9 行開始,程序對兩個被新創建為具有同樣字符值的String 實例進行處理。當你看到從第11 行到12 行的輸出結果時,你會再次注意到這兩個對象并不是同一個,但它們的內容是相同的。輸出結果如下:
s0 == s1: false
s0.equals(s1): true
s0 == s1: false
s0 == s1.intern(): true
第14 行所做的是將String 實例的引用s0 存入常量池。當第15 行被處理時,對s1.intern()方法的調用,會簡單地返回引用s0。 這樣一來第17 行和18 行的輸出結果正是我們所期望的,s0 與s1 仍舊是截然不同的兩個String 。實例因此s0==s1 的結果是false。 而s1.intern()返回的是常量池中的引用值即s0 所,以表達式s0==s1.intern()的結果是true。 假如我們希望將實例s1 存入常量池中,我們必須首先設置s0 為null, 然后請求垃圾回收器garbagecollector 回收被指向s0 的String 實例。在s0 被回收后s1.intern()方法的調用,將會把s1存入常量池。
總的來說在執行等式比較時,應該始終使用String.equals(...)方法,而不是==運算符。如果你還是習慣性地使用==運算符,那么intern()方法可以幫助你得到正確的答案。因為當n 和m 均為String 實例的引用時,語句n.equals(m)與n.intern() ==m.intern()得到的結果是一致的。假如你打算充分利用常量池的優勢那么你就應該選擇String.intern()方法。
posted on 2006-01-08 19:24 早餐2塊2 閱讀(1038) 評論(0) 編輯 收藏 所屬分類: java基礎