1. 程式人生 > >List集合去重的一些方法(常規遍歷、Set去重、java8 stream去重、重寫equals和hashCode方法)

List集合去重的一些方法(常規遍歷、Set去重、java8 stream去重、重寫equals和hashCode方法)

利用 src false java8 see eat 基本 style ceo

1. 常規元素去重

碰到List去重的問題,除了遍歷去重,我們常常想到利用Set集合不允許重復元素的特點,通過List和Set互轉,來去掉重復元素。

// 遍歷後判斷賦給另一個list集合,保持原來順序
    public static void ridRepeat1(List<String> list) {
        System.out.println("list = [" + list + "]");
        List<String> listNew = new ArrayList<String>();
        for
(String str : list) { if (!listNew.contains(str)) { listNew.add(str); } } System.out.println("listNew = [" + listNew + "]"); } // set集合去重,保持原來順序 public static void ridRepeat2(List<String> list) { System.out.println(
"list = [" + list + "]"); List<String> listNew = new ArrayList<String>(); Set set = new HashSet(); for (String str : list) { if (set.add(str)) { listNew.add(str); } } System.out.println("listNew = [" + listNew + "]"); }
// Set去重 由於Set的無序性,不會保持原來順序 public static void ridRepeat3(List<String> list) { System.out.println("list = [" + list + "]"); Set set = new HashSet(); List<String> listNew = new ArrayList<String>(); set.addAll(list); listNew.addAll(set); System.out.println("listNew = [" + listNew + "]"); } // Set去重(將ridRepeat3方法縮減為一行) 無序 public static void ridRepeat4(List<String> list) { System.out.println("list = [" + list + "]"); List<String> listNew = new ArrayList<String>(new HashSet(list)); System.out.println("listNew = [" + listNew + "]"); } // Set去重並保持原先順序的兩種方法 public static void ridRepeat5(List<String> list) { System.out.println("list = [" + list + "]"); List<String> listNew = new ArrayList<String>(new TreeSet<String>(list)); List<String> listNew2 = new ArrayList<String>(new LinkedHashSet<String>(list)); System.out.println("listNew = [" + listNew + "]"); System.out.println("listNew2 = [" + listNew2 + "]"); }

除此之外,可以利用java8的stream來實現去重

  //利用java8的stream去重
  List uniqueList = list.stream().distinct().collect(Collectors.toList());
  System.out.println(uniqueList.toString());

上面的方法在List元素為基本數據類型及String類型時是可以的,但是如果List集合元素為對象,卻不會奏效

public class ObjectRidRepeat {
    
    public static void main(String[] args) {
        List<User> userList = new ArrayList<User>();
        userList.add(new User("小黃",10));
        userList.add(new User("小紅",23));
        userList.add(new User("小黃",78));
        userList.add(new User("小黃",10));
        
        //使用HashSet,無序
        Set<User> userSet = new HashSet<User>();
        userSet.addAll(userList);
        System.out.println(userSet);
        
        //使用LinkedHashSet,有序
        List<User> listNew = new ArrayList<User>(new LinkedHashSet(userList));
        System.out.println(listNew.toString());
    }
}

User類結構如下:

技術分享圖片

輸出如下:(沒有去重)

技術分享圖片

2. 對象去重

解決對象去重,可以利用for循環遍歷的方式進行判斷去重,但今天我不準備探究這種方法,要使用的是如下兩種:

2.1 使用Java8新特性stream去重

        //根據name屬性去重
        List<User> unique1 = userList.stream().collect(
                collectingAndThen(
                        toCollection(() -> new TreeSet<>(comparing(User::getName))), ArrayList::new));

        System.out.println(unique1.toString());

        //根據name,age屬性去重
        List<User> unique2 = userList.stream().collect(
                collectingAndThen(
                        toCollection(() -> new TreeSet<>(comparing(o -> o.getName() + ";" + o.getAge()))), ArrayList::new)
        );

        System.out.println(unique2.toString());

輸出如下:

技術分享圖片

2.2 對象中重寫equals()方法和hashCode()方法

在User類中重寫equals()方法和hashCode()方法:

 //重寫equals方法
 @Override
    public boolean equals(Object obj) {
        User user = (User) obj;
        return name.equals(user.getName()) && (age==user.getAge());
    }

//重寫hashCode方法
    @Override
    public int hashCode() {
        String str = name + age;
        return str.hashCode();
    }

當再次執行通過Set去重的方法時,輸出如下:

技術分享圖片

3. equals()方法和hashCode()方法探究

通過最具代表的的String中的equals()方法和hashCode()方法源碼來探究兩個方法的實現

3.1 equals()方法

/**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

比較兩個對象時,首先先去判斷兩個對象是否具有相同的地址,如果是同一個對象的引用,則直接放回true;如果地址不一樣,則證明不是引用同一個對象,接下來就是挨個去比較兩個字符串對象的內容是否一致,完全相等返回true,否則false。

3.2 hashCode()方法

 /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

當equals方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具有相等的哈希碼。

根據《Effective Java》第二版的第九條:覆蓋equals時總要覆蓋hashCode 中的內容,總結如下:

  • 在程序執行期間,只要equals方法的比較操作用到的信息沒有被修改,那麽對這同一個對象調用多次,hashCode方法必須始終如一地返回同一個整數。
  • 如果兩個對象根據equals方法比較是相等的,那麽調用兩個對象的hashCode方法必須返回相同的整數結果。
  • 如果兩個對象根據equals方法比較是不等的,則hashCode方法不一定得返回不同的整數。但是,程序員應該知道,為不相等的對象生成不同整數結果可以提高哈希表的性能。

《Java編程思想》中也有類似總結:

  設計hashCode()時最重要的因素就是:無論何時,對同一個對象調用hashCode()都應該產生同樣的值。如果在講一個對象用put()添加進HashMap時產生一個hashCdoe值,而用get()取出時卻產生了另一個hashCode值,那麽就無法獲取該對象了。所以如果你的hashCode方法依賴於對象中易變的數據,用戶就要當心了,因為此數據發生變化時,hashCode()方法就會生成一個不同的散列碼。

List集合去重的一些方法(常規遍歷、Set去重、java8 stream去重、重寫equals和hashCode方法)