equals方法和==的差別
首先大家知道,String既可以當作一個物件來使用,又可以當作一個基本型別來使用。這裡指的作為一個基本型別來使用只是指使用方法上的,例如String s = "Hello",它的使用方法如同基本型別int一樣,如int i = 1;,而作為一個物件來使用,則是指透過new關鍵字來建立一個新對象,例如String s = new String("Hello")。但是它的內部動作其實還是創造了一個對象,這點稍後會說到。
其次,對String物件的比較方法有待了解。 Java裡物件之間的比較有兩種概念,這裡拿String物件來說:一種是用"=="來比較,這種比較是針對兩個String類型的變數的引用,也就是說如果兩個String類型的變數,它們所引用同一個String物件(即指向同一塊記憶體堆),則"=="比較的結果是true。另一種是用Object物件的equals()方法來比較,String物件繼承自Object,並且對equals()方法進行了重寫。兩個String物件透過equals()方法來比較時,其實就是對String物件所封裝的字串內容進行比較,也就是說如果兩個String物件所封裝的字串內容相同(包括大小寫相同),則equals()方法將傳回true。
現在開始將對String物件的建立做具體的分析:
1、/////////////////////////////// //////////////////////////////////////////
String s1 = new String( "Hello");
String s2 = new String("Hello");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));以上程式碼段的列印結果是:
false
true
這個結果相信大家很好理解,兩個String類型的變數s1和s2都透過new關鍵字分別創造了一個新的String對象,這個new字為創建的每個物件分配一塊新的、獨立的記憶體堆。因此當透過"=="來比較它們所引用的是否是同一個物件時,將會傳回false。而透過equals()方法來比較時,則傳回true,因為這兩個物件所封裝的字串內容是完全相同的。
2、/////////////////////////////////////////////// ////////////////////////////////////
String s1 = new String("Hello");
String s2 = s1;
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
true
這個結果應該更好理解,變數s1還是透過new關鍵字來創建了一個新的String對象,但這裡s2並沒有透過new關鍵字來創建一個新的String對象,而是直接把s1賦值給了s2,也就是把s1的引用賦值給了s2,所以s2所引用的物件其實就是s1所引用的物件。所以透過"=="來比較時,回傳true。既然它們所引用的都是同一個對象,那麼透過equals()方法來比較時,肯定也回傳true,這裡equals()方法其實在對同一個物件進行比較,自己一定等於自己咯。
3、/////////////////////////////////////////////// ////////////////////////////////
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
結果?那麼來分析一下。首先這兩個String物件都是作為一個基本類型來使用的,而不是透過new關鍵字來建立的,因此虛擬機器不會為這兩個String物件分配新的記憶體堆,而是到String緩衝池中來尋找。 先為s1尋找String緩衝池內是否有與"Hello"相同值的String物件存在,此時String緩衝池內是空的,沒有相同值的String物件存在,所以虛擬機會在String緩衝池內建立此String物件,其動作就是new String("Hello");。然後把此String物件的引用賦值給s1。 接著為s2尋找String緩衝池內是否有與"Hello"相同值的String物件存在,此時虛擬機器找到了一個與其相同值的String對象,這個String物件其實就是為s1所建立的String物件。既然找到了一個相同值的對象,那麼虛擬機器就不在為此創建一個新的String對象,而是直接把存在的String物件的參考賦值給s2。 這裡既然s1和s2所引用的是同一個String對象,也就是自己等於自己,所以以上兩種比較方法都會回到ture。 到這裡,對String物件的基本概念應該都已經被理解了。現在我來小結一下:針對String作為一個基本型別來使用:1。如果String作為一個基本類型來使用,那麼我們視此String物件是String緩衝池所擁有的。
2。如果String是作為一個基本類型來使用,並且此時String緩衝池內不存在與其指定值相同的String對象,那麼此時虛擬機將為此建立新的String對象,並存放在String緩衝池內。
3。如果String是作為一個基本類型來使用,並且此時String緩衝池內存在與其指定值相同的String對象,那麼此時虛擬機將不會為此建立新的String對象,而直接傳回已存在的String物件的參考。
針對String作為一個物件來使用:
1。如果String作為一個物件來使用,那麼虛擬機將為此創建一個新的String對象,即為此對象分配一塊新的記憶體堆,並且它並不是String緩衝池所擁有的,即它是獨立的。
理解了以上內容後,請看以下程式碼段:
4、/////////////////////////////// /////////////////////////////////////////////
String s1 = "Hello";
String s2 = new String("Hello");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));;程式碼段的印刷結果是:
false
true
依照上面的小結來進行分析。第一行是把String當作一個基本型別來使用的,因此s1所引用的物件是屬於String緩衝池內的。且此時String緩衝池內並沒有與其值相同的String物件存在,因此虛擬機會為此建立一個新的String對象,即new String("Hello");。第二行是把String當作一個物件來使用的,因此s2所引用的物件不屬於String緩衝池內的,即它是獨立的。透過new關鍵字,虛擬機會為此建立一個新的String對象,即為它分配了一塊新的記憶體堆。因此"=="比較後的結果是false,因為s1和s2所引用的並不是同一個對象,它們是獨立存在的。而equals()方法所傳回的是true,因為這兩個物件所封裝的字串內容是完全相同的。
現在,我相信大家已經完全弄清楚String物件是怎麼一回事了:)但到此並沒有結束,因為String物件還有更深層的應用。
這裡我將分析String物件的intern()方法的應用:
intern()方法將傳回一個字串物件的規格表示法,即一個同該字串內容相同的字串,但是來自於唯一字串的String緩衝池。這聽起來有點拗口,其實它的機制有如以下程式碼區段:
String s = new String("Hello");
s = s.intern();
以上程式碼段的功能實作可以簡單的看成如下程式碼段:
String s = "Hello";
你一定又開始懷疑了?那你可以先看第二個程式碼段。第二個程式碼段的意思就是從String緩衝池內取出一個與其值相同的String物件的引用賦值給s。如果String緩衝池內沒有與其相同值的String物件存在,則在其內為此建立一個新的String物件。那麼第一段程式碼的意思又是什麼呢?我們知道透過new關鍵字所建立的對象,虛擬機會為它分配一塊新的記憶體堆。如果平凡地創建相同內容的對象,虛擬機器同樣會為此分配許多新的記憶體堆,雖然它們的內容是完全相同的。拿String物件來說,如果連續建立10個相同內容的String物件(new String("Hello")),那麼虛擬機器就會為此分配10個獨立的記憶體堆。假設所建立的String物件的字串內容十分大,假設一個Stirng物件封裝了1M大小的字串內容,那麼如果我們建立10個此相同String物件的話,我們將會毫無意義的浪費9M的記憶體空間。我們知道String是final類,它所封裝的是字串常數,因此String物件在建立後其內部(字串)值不能改變,也因此String物件可以被共用。所以對於剛才提到的假設,我們所創建的10個相同內容的String對象,其實我們只需為此創建一個String對象,然後被其它String變數所共享。要實現這種機制,唯一的、簡單的方法就是使用String緩衝池,因為String緩衝池內不會存在相同內容的String物件。而intern()方法就是使用這種機制的途徑。在一個已實例化的String物件上呼叫intern()方法後,虛擬機會在String緩衝池內尋找與此Stirng物件所封裝的字串內容相同值的String對象,然後把引用賦值給引用原來的那個String對象的String類型變數。如果String緩衝池內沒有與此String物件所封裝的字串內容相同值的String物件存在,那麼虛擬機會為此建立一個新的String對象,並把其引用賦值給引用原來的那個String物件的String類型變數。這樣就達到了共享同一個String物件的目的,而原先那個透過new關鍵字建立出來的String物件將被拋棄並被垃圾回收器回收。這樣不但降低了記憶體的使用消耗,提高了性能,在String物件的比較上也同樣更方便了,因為相同的String物件將被共享,所以要判斷兩個String物件是否相同,則只需要使用" =="來比較,而無需再使用equals()方法來比較,這樣不但使用起來更方便,而且也提高了性能,因為String對象的equals()方法將會對字符串內容拆解,然後逐個進行比較,如果字串內容十分大的話,那麼這個比較動作則大大降低了效能。
說到此,大家可能對具體應用還有點模糊,那麼我來舉個簡單的示例,以便闡述以上概念:
假設有一個類,它有一個記錄消息的方法,這個方法記錄用戶傳來的訊息(假設訊息內容可能較大,且重複率較高),並且把訊息依接收順序記錄在一個清單中。我想有些朋友會這樣設計:
import java.util.*;
public class Messages {
ArrayList messages = new ArrayList();
public void record(String msg) {new ArrayList();
public void record(String msg) {anessages. );
}
public List getMessages() {
return messages;
}
}
這個設計方案好嗎?假設我們重複的傳送給record()方法同一個訊息(訊息來自不同的用戶,所以可以視每個訊息為一個new String("...")),而且訊息內容較大,那麼這個設計將會大大浪費記憶體空間,因為訊息列表中記錄的都是新建立的、獨立的String對象,雖然它們的內容都相同。那麼怎麼樣可以對其進行最佳化呢,其實很簡單,請看如下優化後的範例:
import java.util.*;
public class Messages {
ArrayList messages = new ArrayList();public
ArrayList messages = new ArrayList();public
ArrayList messages = new ArrayList();public
ArrayList messages = new ArrayList();public
ArrayList messages = new ArrayList();public
ArrayList messages = new ArrayList();public
void record(String msg) {messages.add(msg.intern());}public List getMessages() {return messages;} ,原先record()方法中的messages.add(msg);程式碼段變成了messages.add(msg.intern());,僅僅對msg參數呼叫了intern()方法,這樣將對重複的訊息進行共享機制,從而降低了記憶體消耗,提高了效能。
這個例子的確有點牽強,但是這裡只是為了闡述以上概念!
至此,String物件的迷霧都被消除了,大家只要牢記這些概念,以後再複雜的String應用都可以建立在此基礎上來進行分析。