String使用的equals方法和==的區別
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
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));
以上代碼段的打印結果是:
true
true
為什么這個結果?那么來分析一下。首先這兩個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) {
messages.add(msg);
}
public List getMessages() {
return messages;
}
}
這種設計方案好嗎?假設我們重復的發送給record()方法同一個消息(消息來自不同的用戶,所以可以視每個消息為一個new String("...")),並且消息內容較大,那么這個設計將會大大浪費內存空間,因為消息列表中記錄的都是新創建的、獨立的String對象,雖然它們的內容都相同。那么怎么樣可以對其進行優化呢,其實很簡單,請看如下優化后的示例:
import java.util.*;
public class Messages {
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應用都可以建立在此基礎上來進行分析。