使用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;
}