1. 程式人生 > 其它 >使用Set集合對List集合進行去重

使用Set集合對List集合進行去重

使用Set集合對List集合進行去重


前段時間正好遇到這樣一個需求:我們的支付系統從對方系統得到儲存明細物件的List集合,儲存的明細物件物件的明細類簡化為如下TradeDetail類,需求是這樣的,我要對稱List集合進行去重,這裡的去重的意思是隻要物件物件中的accountNo賬號是相同的,就認為明細物件是相同的,去重之後要求是List集合或者Set集合。


在進行上面的需求物件去重之前,先來看很簡單的List集合去重:

package com.qdfae.jdk.collections;


import java.math.BigDecimal;

import java.util.ArrayList;

import java.util.Comparator;

import java.util.HashSet;

import java.util.Set;

import java.util.TreeSet;


import org.junit.Test;


import com.qdfae.jdk.domain.TradeDetail;

import com.qdfae.jdk.domain.User;


/**

* 使用Set集合對List集合進行去重

*

* @author hongwei.lian

* @date 2018年3月9日 下午11:15:52

*/

public class SetTest {

/**

*
List集合的泛型為Integer型別

*

* @author hongwei.lian

* @date 2018年3月9日 下午11:32:53

*/

@Test

public void testListToSet1() {

List<Integer> list = new ArrayList<>();

list.add(1);

list.add(2);

list.add(3);

list.add(1);

Set<Integer> set = new HashSet<>(list);

System.out.println("list的個數為:" + list.size() + "個");

list.forEach(System.out::println);

System.out.println("set的個數為:" + set.size() + "個");

set.forEach(System.out::println);

}

/**

*
List集合的泛型為String型別

*

* @author hongwei.lian

* @date 2018年3月9日 下午11:34:15

*/

@Test

public void testListToSet2() {

List<String> list = new ArrayList<>();

list.add("a");

list.add("b");

list.add("c");

list.add("a");

Set<String> set = new HashSet<>(list);

System.out.println("list的個數為:" + list.size() + "個");

list.forEach(System.out::println);

System.out.println("set的個數為:" + set.size() + "個");

set.forEach(System.out::println);

}


/**

*
List集合的泛型為自定義型別User


*
需求是userCode一樣的便是同一個物件

*

* @author hongwei.lian

* @date 2018年3月10日 上午12:32:12

*/

@Test

public void testListToSet3() {

List<User> list = new ArrayList<>();

list.add(new User(1,"使用者一","600001"));

list.add(new User(2,"使用者二","600002"));

list.add(new User(3,"使用者一","600001"));

list.add(new User(4,"使用者一","600001"));

Set<User> set = new HashSet<>(list);

System.out.println("list的個數為:" + list.size() + "個");

list.forEach(System.out::println);

System.out.println("set的個數為:" + set.size() + "個");

set.forEach(System.out::println);

}

}

上面測試使用到的User類原始碼:

package com.qdfae.jdk.domain;


import java.io.Serializable;


/**

* User實體類

*

* @author hongwei.lian

* @date 2018年3月10日 上午12:33:22

*/

public class User implements Serializable {

private static final long serialVersionUID = -7629758766870065977L;


/**

* 使用者ID

*/

private Integer id;

/**

* 使用者姓名

*/

private String userName;

/**

* 使用者程式碼

*/

private String userCode;

public User() {}

public User(Integer id, String userName, String userCode) {

this.id = id;

this.userName = userName;

this.userCode = userCode;

}


public Integer getId() {

return id;

}


public void setId(Integer id) {

this.id = id;

}


public String getUserName() {

return userName;

}


public void setUserName(String userName) {

this.userName = userName;

}


public String getUserCode() {

return userCode;

}


public void setUserCode(String userCode) {

this.userCode = userCode;

}


@Override

public String toString() {

return "User [id=" + id + ", userName=" + userName + ", userCode=" + userCode + "]";

}

}

依次執行上面三個方法的結果是:

testListToSet1()方法結果:


testListToSet2()方法結果:


testListToSet3()方法結果:


上面的testListToSet1()方法和testListToSet2()方法可以去重,那為什麼testListToSet3()方法就不能去重呢?仔細想想就會知道,兩個物件的地址值不一樣,怎麼會認為是相同的去重呢,再往深處想,就會想到Object類的hashCode()方法和equals()方法,這兩個方法決定了兩個物件是否相等。Integer類和String類之所以可以進行去重,是因為這兩個類都重寫了父類Object類中的hashCode()方法和equals()方法,具體的程式碼可以去檢視JDK原始碼,這裡不再贅述。到這裡我們就知道User物件不能去重的原因所在,那麼我們根據需求在User類中重寫hashCode()方法和equals()方法,重寫後的User類原始碼如下:

package com.qdfae.jdk.domain;


import java.io.Serializable;


/**

* User實體類

*

* @author hongwei.lian

* @date 2018年3月10日 上午12:33:22

*/

public class User implements Serializable {

private static final long serialVersionUID = -7629758766870065977L;


/**

* 使用者ID

*/

private Integer id;

/**

* 使用者姓名

*/

private String userName;

/**

* 使用者程式碼

*/

private String userCode;

public User() {}

public User(Integer id, String userName, String userCode) {

this.id = id;

this.userName = userName;

this.userCode = userCode;

}


public Integer getId() {

return id;

}


public void setId(Integer id) {

this.id = id;

}


public String getUserName() {

return userName;

}


public void setUserName(String userName) {

this.userName = userName;

}


public String getUserCode() {

return userCode;

}


public void setUserCode(String userCode) {

this.userCode = userCode;

}


/**

* 針對userCode重寫hashCode()方法

*/

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + ((userCode == null) ? 0 : userCode.hashCode());

return result;

}


/**

* 針對userCode重寫equals()方法

*/

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

User other = (User) obj;

if (userCode == null) {

if (other.userCode != null)

return false;

} else if (!userCode.equals(other.userCode))

return false;

return true;

}


@Override

public String toString() {

return "User [id=" + id + ", userName=" + userName + ", userCode=" + userCode + "]";

}

}

我們再次執行testListToSet3()方法結果:


這一次符合我們的需求,接下里再來看開頭提出的需求。

準備:

TradeDetail類原始碼:

package com.qdfae.jdk.domain;


import java.io.Serializable;

import java.math.BigDecimal;


/**

* 交易明細

*

* @author hongwei.lian

* @date 2018年3月10日 下午2:44:35

*/

public class TradeDetail implements Serializable {

private static final long serialVersionUID = 3386554986241170136L;


/**

* 交易明細主鍵

*/

private Integer id;

/**

* 賬號

*/

private String
accountNo
;

/**

* 賬戶名稱

*/

private String accountName;

/**

* 交易金額(+表示入金,-表示出金)

*/

private BigDecimal balance;


public TradeDetail() {}

public TradeDetail(Integer id, String accountNo, String accountName, BigDecimal balance) {

this.id = id;

this.accountNo = accountNo;

this.accountName = accountName;

this.balance = balance;

}


public Integer getId() {

return id;

}


public void setId(Integer id) {

this.id = id;

}

public String getAccountNo() {

return accountNo;

}


public void setAccountNo(String accountNo) {

this.accountNo = accountNo;

}


public String getAccountName() {

return accountName;

}


public void setAccountName(String accountName) {

this.accountName = accountName;

}


public BigDecimal getBalance() {

return balance;

}


public void setBalance(BigDecimal balance) {

this.balance = balance;

}


/**

* 針對accountNo重寫hashCode()方法

*/

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + ((accountNo == null) ? 0 : accountNo.hashCode());

return result;

}



/**

* 針對accountNo重寫equals()方法

*/

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

TradeDetail other = (TradeDetail) obj;

if (accountNo == null) {

if (other.accountNo != null)

return false;

} else if (!accountNo.equals(other.accountNo))

return false;

return true;

}


@Override

public String toString() {

return "TradeDetail [id=" + id + ", accountNo=" + accountNo + ", accountName=" + accountName + ", balance="

+ balance + "]";

}


}

我們首先來按照上面的想法根據需求重寫TradeDetail類的hashCode()方法和equals()方法,上面已經給出重寫後的TradeDetail類。

我有三種實現方案如下:

package com.qdfae.jdk.collections;


import java.math.BigDecimal;

import java.util.ArrayList;

import java.util.HashSet;

import java.util.List;

import java.util.Map;

import java.util.Set;

import java.util.TreeSet;

import java.util.stream.Collectors;


import org.junit.Before;

import org.junit.Test;


import com.qdfae.jdk.domain.TradeDetail;


/**

* List集合去重

*

* @author hongwei.lian

* @date 2018年3月11日 下午8:54:57

*/

public class DuplicateListTest {

/**

* 儲存沒有去重的明細物件的List集合

*/

private List<TradeDetail> tradeDetailList;

/**

* 儲存去重後的明細物件的List集合

*/

private List<TradeDetail> duplicateTradeDetailList;

/**

* 儲存去重後的明細物件的Set集合

*/

private Set<TradeDetail> tradeDetailSet;

/**

* 初始化tradeDetailList

*

* @author hongwei.lian

* @date 2018年3月11日 下午9:04:45

*/

@Before

public void InitTradeDetailList() {

tradeDetailList = new ArrayList<>();

tradeDetailList.add(new TradeDetail(1, "600010", "賬戶一", new BigDecimal(100.00)));

tradeDetailList.add(new TradeDetail(2, "600011", "賬戶二", new BigDecimal(100.00)));

tradeDetailList.add(new TradeDetail(3, "600010", "賬戶一", new BigDecimal(-100.00)));

tradeDetailList.add(new TradeDetail(4, "600010", "賬戶一", new BigDecimal(-100.00)));

}


/**

* 使用Set介面的實現類HashSet進行List集合去重

*

* HashSet實現類

* 構造方法:

* public TreeSet(Comparator<? super E> comparator)

*

* @author hongwei.lian

* @date 2018年3月11日 下午9:37:51

*/

@Test

public void testDuplicateListWithHashSet() {

//-- 前提是TradeDetail根據規則重寫hashCode()方法和equals()方法

tradeDetailSet = new HashSet<>(tradeDetailList);

tradeDetailSet.forEach(System.out::println);

}

/**

* 使用Map集合進行List集合去重

*

* @author hongwei.lian

* @date 2018年3月11日 下午9:05:49

*/

@Test

public void testDuplicateListWithIterator() {

duplicateTradeDetailList = new ArrayList<>();

Map<String, TradeDetail> tradeDetailMap = tradeDetailList.stream()

.collect(Collectors.toMap(

tradeDetail -> tradeDetail.getAccountNo(),

tradeDetail -> tradeDetail,

(oldValue, newValue) -> newValue));

tradeDetailMap.forEach(

(accountNo, tradeDetail) -> duplicateTradeDetailList.add(tradeDetail)

);

duplicateTradeDetailList.forEach(System.out::println);

//-- 參考文章

//http://blog.jobbole.com/104067/

//https://www.cnblogs.com/java-zhao/p/5492122.html

}

/**

* 使用Set介面的實現類TreeSet進行List集合去重

*

* TreeSet實現類

* 構造方法:

* public TreeSet(Comparator<? super E> comparator)

*

* @author hongwei.lian

* @date 2018年3月11日 下午9:37:48

*/

@Test

public void testDuplicateListWithTreeSet() {

tradeDetailSet = new TreeSet<>(

(tradeDetail1, tradeDetail2)

->

tradeDetail1.getAccountNo().compareTo(tradeDetail2.getAccountNo())

);

tradeDetailSet.addAll(tradeDetailList);

tradeDetailSet.forEach(System.out::println);

}


}

執行上面三個方法的結果都是:


方案一:根據需求重寫自定義類的hashCode()方法和equals()方法

這種方案的不足之處是根據需求重寫後的hashCode()方法和equals()方法不一定滿足其他需求,這樣這個TradeDetail類的複用性就會相當差。

方案二:遍歷List集合,取出每一個明細物件,將明細物件的accountNo屬性欄位作為Map集合key,明細物件作為Map集合的value,然後再遍歷Map集合,得到一個去重後的List集合或者Set集合。

這種方案的不足之處是消耗效能,首先是List集合去重轉換為Map集合,Map集合再次轉換為List集合或者Set集合,遍歷也會消耗效能。

方案三:使用TreeSet集合的獨有的構造方法進行去重,如下:

public TreeSet(Comparator<? super E> comparator) {

this(new TreeMap<>(comparator));

}

這種方案目前為止是我使用的比較多的方案,不足之處暫時沒有發現,TreeSet集合實際上是利用TreeMap的帶有一個比較器引數的構造方法實現,看JDK原始碼很清晰,最重要的是這個引數Comparator介面,這個介面的原始碼:

Comparator介面部分原始碼:

@FunctionalInterface

public interface Comparator<T> {


int compare(T o1, T o2);


}

這個compare()方法需要自己根據需求去實現,仔細看上面去重的原理實際上還是使用String類的compareTo()方法,String類的compareTo()方法原始碼:

public int compareTo(String anotherString) {

int len1 = value.length;

int len2 = anotherString.value.length;

int lim = Math.min(len1, len2);

char v1[] = value;

char v2[] = anotherString.value;


int k = 0;

while (k < lim) {

char c1 = v1[k];

char c2 = v2[k];

if (c1 != c2) {

return c1 - c2;

}

k++;

}

return len1 - len2;

}