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

如上圖所示,通過 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM