前言部分
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 的處理規則:
希望這篇文章對你有所幫助。博客園持續更新,歡迎關注。