1. 程式人生 > >JAVA 容器--比較大小與比較重複(2)

JAVA 容器--比較大小與比較重複(2)

承接上文:

Comparable介面:針對排序list

問題:上面的演算法根據什麼確定容器中物件的“大小”順序?

所有可以“排序”的類都實現了java.lang.Comparable介面,Comparable介面中只有一個方法

Publicint compareTo(Object obj)

返回0:表示this==obj

返回正數:表示this>obj

返回負數:表示this<obj

實現了Comparable介面的類通過實現comparaTo方法從而確定該物件的排序方式

equals()與hashCode():保證不重複

由來:

要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢?這就是Object.equals方法了。但是,如果每增加一個元素就檢查一次,那麼當元素很多時,後新增到集合中的元素比較的次數就非常多了。也就是說,如果集合中現在已經有1000個元素,那麼第1001個元素加入集合時,它就要呼叫1000次equals方法。這顯然會大大降低效率。  

    於是,Java採用了雜湊表的原理。雜湊演算法也稱為雜湊演算法,是將資料依特定演算法直接指定到一個地址上。

    這樣一來,當集合要新增新的元素時,先呼叫這個元素的hashCode方法,就一下子能定位到它應該放置的物理位置上。如果這個位置上沒有元素,它就可以直接儲存在這個位置上,不用再進行任何比較了;如果這個位置上已經有元素了,就呼叫它的equals方法與新元素進行比較,相同的話就不存了,不相同就雜湊其它的地址。所以這裡存在一個衝突解決的問題。這樣一來實際呼叫equals方法的次數就大大降低了,幾乎只需要一兩次。

規則:

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

      1.如果兩個物件相同,那麼它們的hashCode值一定要相同;

      2.如果兩個物件的hashCode相同,它們並不一定相同(這裡說的物件相同指的是用eqauls方法比較)。如不按要求去做了,會發現相同的物件可以出現在Set集合中,同時,增加新元素的效率會大大下降。

      3.equals()相等的兩個物件,hashcode()一定相等;equals()不相等的兩個物件,卻並不能證明他們的hashcode()不相等。

    在object類中,hashcode()方法是本地方法,返回的是物件的地址值,而object類中的equals()方法比較的也是兩個物件的地址值,如果equals()相等,說明兩個物件地址值也相等,當然hashcode()也就相等了;

在String類中,equals()返回的是兩個物件內容的比較,當兩個物件內容相等時Hashcode()方法根據String類的重寫程式碼的分析,也可知道hashcode()返回結果也會相等。

以此類推,可以知道Integer、Double等封裝類中經過重寫的equals()和hashcode()方法也同樣適合於這個原則。當然沒有經過重寫的類,在繼承了object類的equals()和hashcode()方法後,也會遵守這個原則。

總結:

    hashCode()方法被用來獲取給定物件的唯一整數。這個整數被用來確定物件被儲存在HashTable類似的結構中的位置。預設的,Object類的hashCode()方法返回這個物件儲存的記憶體地址的編號。 hashCode()和equals()定義在Object類中,這個類是所有java類的基類,所以所有的java類都繼承這兩個方法。

    如果我們不重寫這兩個方法,將幾乎不遇到任何問題,但是有的時候程式要求我們必須改變一些物件的預設實現。

舉例說明:

public class TestString{
	public static void main(String[] args){
		String s1="Hello";
		String s2="World";
		String s3="Hello";
		System.out.println(s1 == s3);
		
		s1=new String("hello");
		s2=new String("hello");
		System.out.println(s1 == s2);
		System.out.println(s1.equals(s2));
		
		
		}
}



以上的正確執行是因為我們已經預設重寫了String類的equals()方法和hashCode()方法。但是如果是Object類呢?

import java.util.HashSet;
import java.util.Set;

public class TestEquals1{
	public static void main(String[] args){
			Cat cl=new Cat(1,2,3);
			Cat c2=new Cat(1,2,3);
			System.out.println(cl==c2);
			System.out.println(cl.equals(c2));
			
		}
	}

class Cat{
		
		int color,height,weight;
		public Cat(int color,int height,int weight){
			
			this.color=color;
			this.height=height;
			this.weight=weight;
			}
		
}

毫無疑問,上面的程式將輸出false,但是,事實上上面兩個物件代表的是通過一個cat。真正的商業邏輯希望我們返回true。重寫equals()方法!
public class TestEquals{
	public static void main(String[] args){
			Cat cl=new Cat(1,2,3);
			Cat c2=new Cat(1,2,3);
			System.out.println(cl==c2);
			System.out.println(cl.equals(c2));
		}
	}

class Cat{
		
		int color,height,weight;
		public Cat(int color,int height,int weight){
			
			this.color=color;
			this.height=height;
			this.weight=weight;
			}
		public boolean equals(Object obj){
			if(obj==null)return false;
			else{
				if(obj instanceof Cat){
					Cat c=(Cat)obj;
					if(c.color==this.color && c.height==this.height && c.weight==this.weight){
						return true;
						}
					}
				}
				return false;
			}
	}



So are we done?沒有,讓我們換一種測試方法來看看。

上面的程式輸出的結果是兩個。如果兩個cat物件equals返回true,Set中應該只儲存一個物件才對,而且System.out.println(cat.contains(newCat(1,2,3)));判斷是否存在時,結果輸出為false,如圖:


那麼問題在哪裡呢?

我們忘掉了第二個重要的方法hashCode()。就像JDK的Javadoc中所說的一樣,如果重寫equals()方法必須要重寫hashCode()方法。我們加上下面這個方法,程式將執行正確。


public int hashCode()
     {
      final int PRIME =31;
      int result = 1;
      result = PRIME * result ;
      return result;
}



再總結:

以上這些都是針對容器來說的(如何判斷set中不重複,如何判斷list中的順序),當然最主要的是解釋為什麼重寫equals()方法必須要重寫hashCode()方法的問題。

    根據一個類的equals方法(改寫後),兩個截然不同的例項有可能在邏輯上是相等的,但是,根據Object.hashCode方法,它們僅僅是兩個物件。因此,違反了“相等的物件必須具有相等的雜湊碼”。

    所以只要重寫了equals(),一定要重寫hashCode,否則Hash表都會失效,工作不正常。即便你用equals方法比較得到兩個物件是相等的結論那你也得不到相同的雜湊碼

    即如果cat類只重寫了equals(),hashcode沒有被重寫,加入元素時使用的hashcode()是繼承於set<-collection<-object的,所以計算的hashcode值不同,儲存位置不同,則認為元素不相同,也就能明白為什麼上面的判斷是否存在(System.out.println(cat.contains(newCat(1,2,3))))時輸入的結果為false了。