天天看點

Java中的Set對象去重

前言部分

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() 方法:

Java中的Set對象去重

如上圖所示,通過 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 的處理規則:

Java中的Set對象去重
希望這篇文章對你有所幫助。部落格園持續更新,歡迎關注。

部落格園:https://www.cnblogs.com/niceyoo