聽說你還搞不定java中的==和equals?

相信很多讀者關於==equals懂了又懵,懵了又懂,如此循環,事實上可能是因為看到的部落格文章之類的太多了,長篇大論,加上一段時間的洗禮之後就迷路了。本篇文章再一次理清楚。當然如果覺得本文太啰嗦的話,當然我也考慮到了,因為我也不喜歡長篇大論啰里啰嗦比比叨叨胡攪蠻纏的文章,畢竟大家入門java 的時候就知道個大概了,因此記住一句話就好了:equals本身和 == 沒有區別,對於基本數據都是比較值,對於引用類型,則比較的是所指向的對象的地址!其他類在繼承Object類之後對equals方法重寫,所以表現的是比較裡面的內容!具體比較的則要看自己是怎麼重寫的。

好了,如果有興趣的就看下文,當然不感興趣的大佬可以點個贊直接走了,不用看了,會了還看個*啊,樓主你個憨憨(皮一下很開心)

1、原生的equals()方法本身與 「 == 」沒有任何區別!

從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()方法本身與 「 == 」沒有任何區別,那麼我們對運算符 「 == 」的使用有所了解即可!

運算符 「 == 」其具體作用是用來比較值是否相等,這裡分兩中情況:

  • 1、基本數據類型的變數,則直接比較其存儲的 「值」是否相等;

  • 2、引用類型的變數,則比較的是所指向的對象的地址是否相等;

到這裡我們可以初步確認原生的equals()方法本身與 「 == 」沒有任何區別!作用正是如上。

2、equals()方法的重寫

但是重點來了,因為對於equals()方法我一直在強調原生二字。是的,讓很多初學者疑惑的點就在這裡:equals()方法的重寫!

在JDK中,諸如StringDate等類對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指向它。

3、為什麼要重寫equals方法?

不知道大家有沒有想過這個問題。當然答案也是很簡單的,因為程式設計師比較字元串一般比較其內容就好了,比較記憶體地址是不是同一個對象就好像沒啥意義了,重寫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方法的同時又衍生了下面一個問題。

4、重寫equals方法之後要不要重寫hashCode()方法?

當然這個問題要討論,又要長篇大論嗶嗶一大堆了,有空寫一篇這樣的文章吧專門討論討論這個問題,當然園子里的大佬們也寫了一大堆!可以自行去了解了解。本篇文章簡單聊聊,點到即可。

首先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來實現的,所以這裡只看HashMapput方法即可。源碼如下:

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集合

5、eqauls方法和hashCode方法關係

Java對於eqauls方法和hashCode方法是這樣規定的:

1、如果兩個對象equals為true ,他們的hashcode一定相等。
2、如果兩個對象equals為false,他們的hashcode有可能相等。
3、如果兩個對象hashcode相等,equals不一定為true。
4、如果兩個對象hashcode不相等,equals一定為false。

最後,若有不足或者不正之處,歡迎指正批評,感激不盡!

歡迎各位關注我的公眾號,裡面有一些java學習資料和一大波java電子書籍,比如說周志明老師的深入java虛擬機、java編程思想、核心技術卷、大話設計模式、java並發編程實戰…..都是java的聖經,不說了快上Tomcat車,咋們走!最主要的是一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

在這裡插入圖片描述