Java基礎: HashSet 與 hashCode、equals

大家都說 Java 很簡單,的確 Java 入門不難,但是要想深入瞭解 Java 那不是一朝一夕能夠做到的!

學習 Java 最重要的一點是要學習其設計思想和設計理念,比如集合框架、IO框架的設計等。


通過一個實例談談 HashSet 與 hashCode、equals 的使用,以及在使用時的注意事項。


設計一個 Person 類,如下:

  1. package mark.zhang;  
  2.   
  3. public class Person {  
  4.   
  5.     private String name;  
  6.     private int age;  
  7.   
  8.     public Person(String name, int age) {  
  9.         super();  
  10.         this.name = name;  
  11.         this.age = age;  
  12.     }  
  13.   
  14.     public String getName() {  
  15.         return name;  
  16.     }  
  17.   
  18.     public int getAge() {  
  19.         return age;  
  20.     }  
  21.   
  22.     public void setName(String name) {  
  23.         this.name = name;  
  24.     }  
  25.   
  26.     public void setAge(int age) {  
  27.         this.age = age;  
  28.     }  
  29.   
  30.     @Override  
  31.     public String toString() {  
  32.         return "age=" + age + ", name=" + name;  
  33.     }  
  34.   
  35. }  


這個類很簡單,兩個成員變量以及 set、get 方法,注意這裏沒有重寫 equals、hashCode 方法。爲了在打印的時候方便看出結果,重寫 toString 方法。


測試類也照樣很簡單,如下:

  1. public class TestPerson {  
  2.   
  3.     public static void main(String[] args) {  
  4.         Set<Person> set = new HashSet<Person>();  
  5.         Person p1 = new Person("喜洋洋"25);  
  6.         Person p2 = new Person("懶洋洋"26);  
  7.         Person p3 = new Person("灰太郎"27);  
  8.         set.add(p1);  
  9.         set.add(p2);  
  10.         set.add(p3);  
  11.         System.out.println(set.size() + " 個動畫人物!");  
  12.   
  13.         for (Person person : set) {  
  14.             System.out.println(person);  
  15.         }  
  16.     }  
  17. }  

輸出結果,如下所示:

  1. 3 個動畫人物!  
  2. age=27, name=灰太郎  
  3. age=26, name=懶洋洋  
  4. age=25, name=喜洋洋  

ok,看懂上面的程序很簡單,只要你不是初學 Java 的話!但是今天的主題不是隻討論這段代碼的難易程度。

如果在代碼中刪除一個“人”,很簡單,只需要調用 remove 方法即可,如下所示:

  1. set.remove(p2);  


這個時候,我需要修改 Person 這個類,重寫父類 Object 的兩個方法,equals、hashCode,修改之後的代碼:

  1. package mark.zhang;  
  2.   
  3. public class Person {  
  4.   
  5.     private String name;  
  6.     private int age;  
  7.   
  8.     public Person(String name, int age) {  
  9.         super();  
  10.         this.name = name;  
  11.         this.age = age;  
  12.     }  
  13.   
  14.     public String getName() {  
  15.         return name;  
  16.     }  
  17.   
  18.     public int getAge() {  
  19.         return age;  
  20.     }  
  21.   
  22.     public void setName(String name) {  
  23.         this.name = name;  
  24.     }  
  25.   
  26.     public void setAge(int age) {  
  27.         this.age = age;  
  28.     }  
  29.   
  30.     @Override  
  31.     public String toString() {  
  32.         return "age=" + age + ", name=" + name;  
  33.     }  
  34.   
  35.     @Override  
  36.     public int hashCode() {  
  37.         final int prime = 31;  
  38.         int result = 1;  
  39.         result = prime * result + age;  
  40.         result = prime * result + ((name == null) ? 0 : name.hashCode());  
  41.         return result;  
  42.     }  
  43.   
  44.     @Override  
  45.     public boolean equals(Object obj) {  
  46.         if (this == obj)  
  47.             return true;  
  48.         if (obj == null)  
  49.             return false;  
  50.         if (getClass() != obj.getClass())  
  51.             return false;  
  52.         Person other = (Person) obj;  
  53.         if (age != other.age)  
  54.             return false;  
  55.         if (name == null) {  
  56.             if (other.name != null)  
  57.                 return false;  
  58.         } else if (!name.equals(other.name))  
  59.             return false;  
  60.         return true;  
  61.     }  
  62.   
  63. }  

在測試類中,開始這樣子做:

  1. public class TestPerson {  
  2.   
  3.     public static void main(String[] args) {  
  4.         Set<Person> set = new HashSet<Person>();  
  5.         Person p1 = new Person("喜洋洋"25);  
  6.         Person p2 = new Person("懶洋洋"26);  
  7.         Person p3 = new Person("灰太郎"27);  
  8.         set.add(p1);  
  9.         set.add(p2);  
  10.         set.add(p3);  
  11.         System.out.println(set.size() + " 個動畫人物!");  
  12.         // 刪除一個對象  
  13.         set.remove(p2);  
  14.         System.out.println("刪除之後," + set.size() + " 個動畫人物!");  
  15.         for (Person person : set) {  
  16.             System.out.println(person);  
  17.         }  
  18.     }  
  19. }  

打印結果:

  1. 3 個動畫人物!  
  2. 刪除之後,2 個動畫人物!  
  3. age=27, name=灰太郎  
  4. age=25, name=喜洋洋  

成功刪除一個對象,再次修改測試類的代碼:

  1. public class TestPerson {  
  2.   
  3.     public static void main(String[] args) {  
  4.         Set<Person> set = new HashSet<Person>();  
  5.         Person p1 = new Person("喜洋洋"25);  
  6.         Person p2 = new Person("懶洋洋"26);  
  7.         Person p3 = new Person("灰太郎"27);  
  8.         set.add(p1);  
  9.         set.add(p2);  
  10.         set.add(p3);  
  11.         System.out.println(set.size() + " 個動畫人物!");  
  12.         // 修改對象屬性  
  13.         p2.setName("美人魚");  
  14.         // 刪除一個對象  
  15.         set.remove(p2);  
  16.         System.out.println("刪除之後," + set.size() + " 個動畫人物!");  
  17.         for (Person person : set) {  
  18.             System.out.println(person);  
  19.         }  
  20.     }  
  21. }  

打印結果:

  1. 3 個動畫人物!  
  2. 刪除之後,3 個動畫人物!  
  3. age=26, name=美人魚  
  4. age=27, name=灰太郎  
  5. age=25, name=喜洋洋  

這次怪了,明明刪除一個了,怎麼還是有三個呢?你會發現,的確刪除一個“懶洋洋”,但是“美人魚”沒有被刪除!

如果你在 Person 類中,不重寫 hashCode 方法,不會有這種現象發生!


這裏說明一個問題:添加到集合的類,不要輕易去修改該類對象的屬性,否則 remove() 方法無效。同理 contains() 方法也會無效。


如果有興趣的話,可以看看其源碼,可以看出這與 hashCode() 方法有很大關係!


再說一個容易讓人誤解的問題:

Collection接口的子接口 List 和 Set,Set (包括其子類)無序不可重複,List (包括其子類)有序可重複,所謂有序無序是相對於 add 的程序執行順序來說的。


換句話說,對於上面的 List、Set 以及其子類等,如果 equals 爲 true 的話,就算是重複的對象。這裏的 equals 比較的是內容,不是對象地址。
只不過,對於 Set 來說不可以添加重複對象,對於 List 來說可以添加重複對象!


對於添加對象到Set集合中,從源碼可以看出其流程是這樣子的:


將對象放入到集合中時,首先判斷要放入對象的hashcode值與集合中的任意一個元素的hashcode值是否相等,如果不相等直接將該對象放入集合中。
如果hashcode值相等,然後再通過equals方法判斷要放入對象與集合中的任意一個對象是否相等,如果equals判斷不相等,直接將該元素放入到集合中,否則不放入。


舉個例子,注意:Person 要重寫 hashCode、equals 方法:

  1. public static void main(String[] args) {  
  2.         LinkedList<Person> list = new LinkedList<Person>();  
  3.         Set<Person> set = new HashSet<Person>();  
  4.         Person p1 = new Person("喜喜"3);  
  5.         Person p2 = new Person("喜喜"3);  
  6.         System.out.println("stu1 == stu2 : " + (p1 == p2));  
  7.         System.out.println("stu1.equals(stu2) : " + p1.equals(p2));  
  8.         // list可以重複  
  9.         list.add(p1);  
  10.         list.add(p2);  
  11.         System.out.println("list size:" + list.size());  
  12.         // set 不可以重複  
  13.         set.add(p1);  
  14.         set.add(p2);  
  15.         System.out.println("set size:" + set.size());  
  16.     }  


打印結果:

  1. stu1 == stu2 : false  
  2. stu1.equals(stu2) : true  
  3. list size:2  
  4. set size:1  

感謝下面兩篇博客,我只是在它們的基礎之上添枝加葉。


再次感謝:


hashCode與equals的區別與聯繫


Java集合HashSet的hashcode方法引起的內存泄漏問題


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章