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

    }

     
  • 相关阅读:
    第二章、Redis入门介绍
    最高的牛
    增减序列
    激光炸弹
    分形之城
    约数之和
    奇怪的汉诺塔
    费解的开关
    递归实现排列型枚举
    递归实现组合型枚举
  • 原文地址:https://www.cnblogs.com/wangguanyi/p/15407777.html
Copyright © 2020-2023  润新知