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

}

 
原文地址:https://www.cnblogs.com/wangguanyi/p/15407777.html