前言部分
Set<T>
去重相信大家一定不陌生,尤其是在
Set<String>
、
Set<Integer>
等等,但是在使用 Set<實體> ,在不重寫 equals()、hashCode() 方法情況下,直接使用貌似并不能生效。
是以想要 Set<實體> 實作去重,核心部分在實體中重寫 equals()、hashCode() 方法。
如下以 User 實體為例,進行測試。
代碼部分
測試代碼:
public static void main(String[] args) {
Set<User> userSet = new HashSet<User>(){{
add(new User("張三",10));
add(new User("張三",20));
add(new User("張三",10));
}};
userSet.forEach(user -> {
System.out.println(String.format("name: %s, age:%s",user.getName(),user.getAge()));
});
}
列印結果:
name: 張三, age:20
name: 張三, age:10
實體對象(User.java):
重寫了 equals()、hashCodd() 方法。
public class User {
public User(String name, Integer age){
this.name = name;
this.age = age;
}
/** 姓名 **/
private String name;
/** 年齡 **/
private Integer age;
省略get、set方法...
/**
* 重寫equals方法,如果對象類型是User,先比較hashcode,一緻的場合再比較每個屬性的值
*/
@Override
public boolean equals(Object obj) {
System.out.println("調用equals方法,目前的hashCode為:"+hashCode());
/** 對象是 null 直接傳回 false **/
if (obj == null) {
return false;
}
/** 對象是目前對象,直接傳回 true **/
if (this == obj) {
return true;
}
/** 判斷對象類型是否是User **/
if (obj instanceof User) {
User vo = (User) obj;
/** 比較每個屬性的值一緻時才傳回true **/
/** 有幾個對象就要比較幾個屬性 **/
if (vo.name.equals(this.name) && vo.age.equals(this.age)) {
return true;
}
}
return false;
}
/**
* 重寫hashcode方法,傳回的hashCode一樣才再去比較每個屬性的值
*/
@Override
public int hashCode() {
return this.getName().hashCode() * this.getAge().hashCode();
}
}
解釋部分
為什麼
Set<String>
Set<Integer>
就可以直接實作去重,而 Set<實體> 就不可以,反而要重寫 equals()、hashCode() 方法才能實作,更甚者是,隻重寫 equals() 方法,而不重寫 hashCode() 方法都沒法完成去重~
大家對這個問題有過疑惑嗎?
1、HashSet 添加資料過程
HashSet 的底層實作,相信大家都清楚是 HashMap 吧?我們在 add() 資料時,其實一層層找,最終是調的 HashMap 的 put() 方法,如下是 HashSet 的 add() 方法,其中 map 為 HashMap。
我們再點一層找到 HashMap 的 put() 方法:

如上圖所示,通過 putVal() 方法我們大緻有了個概念了,判斷是否為舊值就是對 hash 值、key 值進行比較。
hash 值比較自然調用的事 hashCode() 方法,而 key 值的比較實用的是 equals() 方法。
了解到這基本就可以看出 hashCode() 、equals() 方法對于去重的重要性了。
2、Set<單屬性> 可以直接使用去重
那麼接下來我們就可以來看看 Set<單屬性>(單屬性:String、Integer等),為什麼直接使用就可以去重了。
我們以 String 為例,假設有兩個字元串 a、b,如下:
String a = "123";
String b = "123";
System.out.println("a.hashCode:"+a.hashCode());
System.out.println("b.hashCode:"+b.hashCode());
System.out.println(a.equals(b));
列印結果如下:
a.hashCode:48690
b.hashCode:48690
true
很顯然,在沒有重寫 hashCode() 、equals() 方法時,字元串 a、b 的 hashCode,equalse() 是一緻的,那麼這兩個就可以視為一個對象,是以用在 Set 裡面就可以直接去重。
但是為什麼會一緻呢?
任何對象在不重寫 equals()、hashcode() 的情況下,使用的是 Object 對象的 equals() 方法和 hashcode() 方法,而重點就是,預設的 equals() 方法判斷的是兩個對象的引用指向的是不是同一個對象;而 hashcode 也是根據對象位址生成一個整數數值;
顯然字元串 a、b 這兩個條件都滿足,是以對于 Set 來說就是一個對象的概念。
3、Set<實體> 去重
但是換到對于實體對象就行不通了,我們再來套 Object 的 equals()、hashCode() 方法。
當我們
new User()
對象時,兩個對象的位址引用肯定是不同的;其次 hashcode 是根據對象位址生成的,這樣顯然也不同,是以對于 Set 來說,那麼去重就行不通。
是以,想要讓 Set<實體> 實作去重效果,那麼就需要重寫 equals() 、hashCode() 方法。
隻有兩個對象的 hashCode() 方法的值一緻,且 equalse() 方法傳回 true,那麼這對于 Set<實體> 來說就可以看做一個對象, 如果兩者隻滿足一個是不可以的(隻重寫一個),舉個例子:
equales()重寫,hashCode()不重寫
@Override
public boolean equals(Object obj) {
return true;
}
//@Override
//public int hashCode() {
// return this.getName().hashCode() * this.getAge().hashCode();
//}
執行代碼:
Set<User> userSet = new HashSet<User>(){{
add(new User("張三",10));
add(new User("張三",20));
add(new User("張三",10));
}};
userSet.forEach(user -> {
System.out.println(String.format("name: %s, age:%s",user.getName(),user.getAge()));
});
列印内容:
name: 張三, age:10
name: 張三, age:10
equales()不重寫,hashCode()重寫
//@Override
//public boolean equals(Object obj) {
// return true;
//}
@Override
public int hashCode() {
return this.getName().hashCode() * this.getAge().hashCode();
}
執行代碼+列印内容如上:
name: 張三, age:10
name: 張三, age:10
總結
總之,要想保證 Set<實體> 實作去重,就需要兩個實體 “一緻”,這裡的一緻是隻需要滿足如下兩個條件:
- 重寫 hashCode() 方法,確定兩者 hashcode 一緻,比如使用屬性相乘或者相加。
- 重寫 equals() 方法,相同對象、屬性值相同對象皆為相等。
通過上面這些例子也能看出重寫 equals 方法,就必須重寫 hashCode 的重要性,因為隻重寫 equals() 不一定能滿足預期相等的效果。
如下是阿裡巴巴開發手冊,關于 hashCode 和 equals 的處理規則:
希望這篇文章對你有所幫助。部落格園持續更新,歡迎關注。
部落格園:https://www.cnblogs.com/niceyoo