相信不少讀者關於==
和equals
懂了又懵,懵了又懂,如此循環,事實上多是由於看到的博客文章之類的太多了,長篇大論,加上一段時間的洗禮以後就迷路了。本篇文章再一次理清楚。固然若是以爲本文太囉嗦的話,固然我也考慮到了,由於我也不喜歡長篇大論囉裏囉嗦比比叨叨胡攪蠻纏的文章,畢竟你們入門java 的時候就知道個大概了,所以記住一句話就行了:equals自己和 == 沒有區別,對於基本數據都是比較值,對於引用類型,則比較的是所指向的對象的地址!其餘類在繼承Object類以後對equals方法重寫,因此表現的是比較裏面的內容!具體比較的則要看本身是怎麼重寫的。javascript
好了,若是有興趣的就看下文,固然不感興趣的大佬能夠點個贊直接走了,不用看了,會了還看個*啊,樓主你個憨憨(皮一下很開心)java
從java語言本質上來說,"=="屬於JAVA語言的運算符,而equals則是根類Object的一個方法。程序員
關於Object類的equals()方法,咱們能夠看看其源碼算法
/* * @param obj the reference object with which to compare. * @return {@code true} if this object is the same as the obj * argument; {@code false} otherwise. * @see #hashCode() * @see java.util.HashMap */
public boolean equals(Object obj) {
return (this == obj);
}
複製代碼
是的,equals底層其實就是「 == 」,也就是說,原生的equals()方法自己與 「 == 」沒有任何區別!惟一的區別則是基本類型沒有繼承Object類,因此基本類型沒有equals()方法,也就是說基本類型只能使用「 == 」判斷值是否相等。編程
既然原生的equals()方法自己與 「 == 」沒有任何區別,那麼咱們對運算符 「 == 」的使用有所瞭解便可!設計模式
運算符 「 == 」其具體做用是用來比較值是否相等,這裏分兩中狀況:併發
一、基本數據類型的變量,則直接比較其存儲的 「值」是否相等;app
二、引用類型的變量,則比較的是所指向的對象的地址是否相等;框架
到這裏咱們能夠初步確認原生的equals()方法自己與 「 == 」沒有任何區別!做用正是如上。ide
可是重點來了,由於對於equals()
方法我一直在強調原生二字。是的,讓不少初學者疑惑的點就在這裏:equals()方法的重寫!
在JDK中,諸如String
、Date
等類對equals
方法進行了重寫,以String爲例,這裏感興趣的讀者能夠一塊兒看看String類中重寫的equals()方法,固然跳過也問題不大
/** * Compares this string to the specified object. The result is {@code * true} if and only if the argument is not {@code null} and is a {@code * String} object that represents the same sequence of characters as this * object. * * @param anObject * The object to compare this {@code String} against * * @return {@code true} if the given object represents a {@code String} * equivalent to this string, {@code false} otherwise * * @see #compareTo(String) * @see #equalsIgnoreCase(String) */
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
複製代碼
從源碼中能夠看出,首先該方法判斷比較的是所指向的對象地址是否相等,若是相同直接返回true
,若是不相同進行下一個if
判斷,第二個if
判斷大體意思則是比較其存儲的 「值」是否相等,也就是比較內容值!相同就返回true,好比兩個new String對象「AAA」
和「AAA」
,這裏雖然對象地址不相等,可是內容相等,因此一樣返回true。
這裏我就想給各位出個典型的String例子了:
public static void main(String[] args) {
String a = "宜春";
String b = new String("宜春");
String c = b; //注意這裏是引用傳遞,意思是c也指向b指向的內存地址
System.out.println(a == b); //false
System.out.println(a == c); //false
System.out.println(b == c); //true
System.out.println(a.equals(b)); //true
System.out.println(a.equals(c)); //true
System.out.println(b.equals(c)); //true
}
複製代碼
【特別注意:String類型屬於引用類型】
解析: (1)a == b?意思是地址指向的是同一塊地方嗎?很明顯不同。
(2)a == c?意思是地址指向的是同一塊地方嗎?很明顯不同。
(3)b == c?意思是地址指向的是同一塊地方嗎?很明顯內容同樣,因此爲true。
(4)a.equals( b )?意思是地址指向的內容同樣嘛?同樣。
(4)a.equals( c )?意思是地址指向的內容同樣嘛?同樣。
(4)b.equals( c )?意思是地址指向的內容同樣嘛?同樣。
固然,你可能仍是有點疑惑,那麼結合下面這張圖再理解上面的解析,你可能就恍然大悟了
OK。如今能理解嘛?你你你......不用回答,我知道你理解了(義正詞嚴)。固然值得注意一點的是String中intern()
方法,先看一段程序:
public static void main(String[] args) {
String a = "宜春";
String b = new String("宜春");
b=b.intern();
System.out.println(a == b); //true
System.out.println(a.equals(b)); //true
}
複製代碼
intern
方法的意思是檢查字符串池裏是否存在,若是存在了那就直接返回爲true。所以在這裏首先a
會在字符串池裏面有一個,而後 b.intern()
一看池子裏有了,就再也不新建new了,直接把b
指向它。
不知道你們有沒有想過這個問題。固然答案也是很簡單的,由於程序員比較字符串通常比較其內容就行了,比較內存地址是否是同一個對象就好像沒啥意義了,重寫equals方法就很方便用來比較字符串的內容了。
其實除了諸如String、Date等類對equals方法進行重寫,咱們在實際開發中,咱們也經常會根據本身的業務需求重寫equals方法.
舉個栗子: 咱們的需求就是若是兩個學生對象姓名、身份證號、性別相等,咱們認爲兩個學生對象相等,不必定須要學生對象地址相同。
學生A的我的信息(姓名:如花,性別:女,身份證號:123,住址:廣州),學生A對象地址爲0x11, 學生B的我的信息(姓名:如花,性別:女,身份證號:123,住址:深圳),學生A對象地址爲0x12,
這時候若是不重寫Object的equals方法,那麼返回的必定是false不相等,這個時候就須要咱們根據本身的需求重寫equals()方法了。具體equals方法的重寫代碼以下:
// 重寫equals方法
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Student)) {
// instanceof 已經處理了obj = null的狀況
return false;
}
Student stuObj = (Student) obj;
// 對象地址相等
if (this == stuObj) {
return true;
}
// 若是兩個對象姓名、身份證號碼、性別相等,咱們認爲兩個對象相等
if (stuObj.name.equals(this.name) && stuObj.sex.equals(this.sex) && stuObj.IDnumber.equals(this.IDnumber)) {
return true;
} else {
return false;
}
}
複製代碼
開發中這樣設計,才能符合咱們的生活!到這裏我就不信了你還搞不定==和equals!
但是一涉及重寫equals方法的同時又衍生了下面一個問題。
固然這個問題要討論,又要長篇大論嗶嗶一大堆了,有空寫一篇這樣的文章吧專門討論討論這個問題,固然園子裏的大佬們也寫了一大堆!能夠自行去了解了解。本篇文章簡單聊聊,點到便可。
首先hashCode()方法是Object類的一個方法,源碼以下:
/** * Returns a hash code value for the object. This method is * supported for the benefit of hash tables such as those provided by * {@link java.util.HashMap}. * <p> * The general contract of {@code hashCode} is: * <ul> * <li>Whenever it is invoked on the same object more than once during * an execution of a Java application, the {@code hashCode} method * must consistently return the same integer, provided no information * used in {@code equals} comparisons on the object is modified. * This integer need not remain consistent from one execution of an * application to another execution of the same application. * <li>If two objects are equal according to the {@code equals(Object)} * method, then calling the {@code hashCode} method on each of * the two objects must produce the same integer result. * <li>It is <em>not</em> required that if two objects are unequal * according to the {@link java.lang.Object#equals(java.lang.Object)} * method, then calling the {@code hashCode} method on each of the * two objects must produce distinct integer results. However, the * programmer should be aware that producing distinct integer results * for unequal objects may improve the performance of hash tables. * </ul> * <p> * As much as is reasonably practical, the hashCode method defined by * class {@code Object} does return distinct integers for distinct * objects. (This is typically implemented by converting the internal * address of the object into an integer, but this implementation * technique is not required by the * Java<font size="-2"><sup>TM</sup></font> programming language.) * * @return a hash code value for this object. * @see java.lang.Object#equals(java.lang.Object) * @see java.lang.System#identityHashCode */
public native int hashCode();
複製代碼
能夠看出hashCode()
方法返回的就是一個int
數值,從方法的名稱上就能夠看出,其目的是生成一個hash
碼。hash碼的主要用途就是在對對象進行散列的時候做爲key
輸入,據此很容易推斷出,咱們須要每一個對象的hash碼儘量不一樣,這樣才能保證散列的存取性能。
事實上,Object類提供的默認實現確實保證每一個對象的hash碼不一樣(在對象的內存地址基礎上通過特定算法返回一個hash碼)。Java採用了哈希表的原理。哈希算法也稱爲散列算法,是將數據依特定算法直接指定到一個地址上。初學者能夠這樣理解,hashCode方法實際上返回的就是對象存儲的物理地址(實際上不是)。
想要知道hashCode的做用,必需要先知道Java中的集合。
Java中的集合List的元素是有序的,元素能夠重複;Set元素無序,但元素不可重複。這咱們都清楚。可是你有沒有想過這樣一個問題:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢?
每錯這裏就是用Object.equals
方法了。可是,若是每增長一個元素就檢查一次,那麼當元素不少時,後添加到集合中的元素比較的次數就很是多了。也就是說,若是集合中如今已經有1000個元素,那麼第1001個元素加入集合時,它就要調用1000次equals
方法。這顯然會大大下降效率。
那怎麼解決呢?咱們能夠在Java集合框架中獲得驗證。因爲HashSet
是基於HashMap
來實現的,因此這裏只看HashMap
的put
方法便可。源碼以下:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
//這裏經過哈希值定位到對象的大概存儲位置
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//if語句中,先比較hashcode,再調用equals()比較
//因爲「&&」具備短路的功能,只要hashcode不一樣,也無需再調用equals方法
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
複製代碼
正如源碼中註釋所述,「&&」具備短路的功能,只要hashcode不一樣,也無需再調用equals方法。是的,Java採用了哈希表的原理。 哈希表具備優越的查詢性能,就像九九乘法表2*3=6
你能很輕易知道,可是哈希表不免還會出現哈希衝突,只是機率極低。
如此設計,這樣一來,當集合要添加新的元素時,先調用這個元素的hashCode方法,就一會兒能定位到它應該放置的物理位置上。 若是這個位置上沒有元素,它就能夠直接存儲在這個位置上,不用再進行任何比較了;若是這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存,不相同就散列其它的地址。因此這裏存在一個衝突解決的問題。這樣一來實際調用equals方法的次數就大大下降了,幾乎只須要一兩次。
所以並非重寫了equals方法就必定要重寫hashCode方法,只有用到HashMap,HashSet等Java集合的時候重寫了equals方法就必定要重寫hashCode方法。用不到哈希表僅僅重寫equals()方法也OK的。
Java官方建議 重寫equals()就必定要重寫hashCode()方法。畢竟實際開發場景中經常用到Java集合
Java對於eqauls方法和hashCode方法是這樣規定的:
一、若是兩個對象equals爲true ,他們的hashcode必定相等。 二、若是兩個對象equals爲false,他們的hashcode有可能相等。 三、若是兩個對象hashcode相等,equals不必定爲true。 四、若是兩個對象hashcode不相等,equals必定爲false。
最後,如有不足或者不正之處,歡迎指正批評,感激涕零!
歡迎各位關注個人公衆號,裏面有一些java學習資料和一大波java電子書籍,好比說周志明老師的深刻java虛擬機、java編程思想、核心技術卷、大話設計模式、java併發編程實戰.....都是java的聖經,不說了快上Tomcat車,咋們走!最主要的是一塊兒探討技術,嚮往技術,追求技術,說好了來了就是盆友喔...