1. 程式人生 > >【轉】STRING使用EQUALS方法和==分別比較的是什麼

【轉】STRING使用EQUALS方法和==分別比較的是什麼

轉自: https://www.cnblogs.com/baotong-9396/p/7182906.html

 

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物件的建立做具體的分析:

/////////////////////////////////////////////////////////////////////////
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,因為這兩個物件所封裝的字串內容是完全相同的。

//////////////////////////////////////////////////////////////////////////////////
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()方法其實在對同一個物件進行比較,自己肯定等於自己咯。

//////////////////////////////////////////////////////////////////////////////
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緩衝池所擁有的,即它是獨立的。

理解了以上內容後,請看以下程式碼段:

/////////////////////////////////////////////////////////////////////////////
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應用都可以建立在此基礎上來進行分析。