Java 怎么判断两个 List 集合是否相等?

 2022-09-26

背景

最近在开发一个数据对比的worker,内容是对比MySQL中的数据是否和ElasticSearch中的数据一致,其中就涉及到List类型的数据对比,有个比较头痛的点,就是要求不考虑集合内元素的顺序。

我们都知道,List是一个有序的集合,简单重写equals、hashCode方法,是会存在元素顺序的判断的,故我们需要写一个专门的方法来进行对比。

202209262255391811.png

方案

方案很简单,对比的时候分4个步骤:

  1. 重写List里边对象的equals、hashCode方法
  2. 非空判断——null
  3. 元素个数判断——size
  4. 元素全部包含判断——containAll

关键就是后面3步吧,前面属于常规操作,下面我以一个例子来说明

(1)含有List类型的对象

    package com.example.springbootcomparer.domain;
    
    import lombok.Data;
    
    import java.util.Date;
    import java.util.List;
    
    /**
     * 书
     *
     * @author hongcunlin
     */
    @Data
    public class Book {
        /**
         * 主键
         */
        private Long id;
    
        /**
         * 书类型
         */
        private Integer type;
    
        /**
         * 书名
         */
        private String name;
    
        /**
         * 创建时间
         */
        private Date createTime;
    
        /**
         * 更新时间
         */
        private Date updateTime;
    
        /**
         * 书扩展字段
         */
        private List<BookExt> bookExtList;
    }

如上面的bookExtList字段,该字段为List类型

(2)重写List里边对象的equals、hashCode方法

    package com.example.springbootcomparer.domain;
    
    import lombok.Data;
    
    import java.util.Date;
    import java.util.Objects;
    
    /**
     * 书扩展
     *
     * @author hongcunlin
     */
    @Data
    public class BookExt {
        /**
         * 主键
         */
        private Long id;
    
        /**
         * 创建类型
         */
        private Integer type;
    
        /**
         * 内容
         */
        private String value;
    
        /**
         * 创建时间
         */
        private Date createTime;
    
        /**
         * 更新时间
         */
        private Date updateTime;
    
        /**
         * 1.1.重写equals
         *
         * @param o 对象
         * @return 是否相等
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            BookExt ext = (BookExt) o;
            return Objects.equals(id, ext.id) && Objects.equals(type, ext.type) && Objects.equals(value, ext.value) && Objects.equals(createTime, ext.createTime) && Objects.equals(updateTime, ext.updateTime);
        }
    
        /**
         * 1.2.重写hashCode
         *
         * @return hash值
         */
        @Override
        public int hashCode() {
            return Objects.hash(id, type, value, createTime, updateTime);
        }
    }

如我们重写了作为List类型的元素BookExt的equals、hashCode两个方法,而编写这两个方法,可以通过IDEA的Generate自动生成。

(3)编写对比服务

    package com.example.springbootcomparer.service.impl;
    
    import com.example.springbootcomparer.domain.Book;
    import com.example.springbootcomparer.domain.BookExt;
    import com.example.springbootcomparer.service.BookCompareService;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    import java.util.Objects;
    
    
    /**
     * @author hongcunlin
     */
    @Service
    public class BookCompareServiceImpl implements BookCompareService {
        @Override
        public boolean compare(Book source, Book target) {
            if (source == null) {
                return target == null;
            } else {
                if (target == null) {
                    return false;
                }
                return Objects.equals(source.getId(), target.getId()) &&
                        Objects.equals(source.getType(), target.getType()) &&
                        Objects.equals(source.getName(), target.getName()) &&
                        Objects.equals(source.getCreateTime(), target.getCreateTime()) &&
                        Objects.equals(source.getUpdateTime(), target.getUpdateTime()) &&
                        compareList(source.getBookExtList(), target.getBookExtList());
            }
        }
    
        /**
         * List类型不考虑顺序,比较是否一样
         *
         * @param source 源
         * @param target 目标
         * @return 是否一样
         */
        private boolean compareList(List<BookExt> source, List<BookExt> target) {
            // 2.非空判断
            if (source == null) {
                return target == null;
            } else {
                if (target == null) {
                    return false;
                }
                // 2.元素个数判断
                if (source.size() != target.size()) {
                    return false;
                }
                // 3.元素全部包含判断
                return source.containsAll(target);
            }
        }
    }

可以看到,首先是进行基础字段的对比,使用Objects类提供的静态方法equals。接着是核心的List类型字段的对比,再compareList中,按照我们说的步骤来。

测试

我们可以编写一个测试方法,来验证下我们的,想法,结果是符合要求的

    package com.example.springbootcomparer;
    
    import com.example.springbootcomparer.domain.Book;
    import com.example.springbootcomparer.domain.BookExt;
    import com.example.springbootcomparer.service.BookCompareService;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import javax.annotation.Resource;
    import java.util.*;
    
    @SpringBootTest
    class SpringBootComparerApplicationTests {
    
        @Resource
        private BookCompareService compareService;
    
        @Test
        void contextLoads() {
            // true
            System.out.println(compareService.compare(buildBookA(), buildBookB()));
        }
    
        private Book buildBookA() {
            Book book = new Book();
            book.setId(1L);
            book.setType(1);
            book.setName("hello");
            book.setCreateTime(new Date());
            book.setUpdateTime(new Date());
            List<BookExt> bookExt = new ArrayList<>();
            // 顺序不一致
            bookExt.add(buildExtA());
            bookExt.add(buildExtB());
            book.setBookExtList(bookExt);
            return book;
        }
    
        private Book buildBookB() {
            Book book = new Book();
            book.setId(1L);
            book.setType(1);
            book.setName("hello");
            book.setCreateTime(new Date());
            book.setUpdateTime(new Date());
    
            List<BookExt> bookExt = new ArrayList<>();
            // 顺序不一致
            bookExt.add(buildExtB());
            bookExt.add(buildExtA());
    //        bookExtList.add(buildExtB());
            book.setBookExtList(bookExt);
            return book;
        }
    
        private BookExt buildExtA() {
            BookExt ext = new BookExt();
            ext.setId(1L);
            ext.setValue("hello");
            return ext;
        }
    
        private BookExt buildExtB() {
            BookExt ext = new BookExt();
            ext.setId(2L);
            ext.setValue("hi");
            return ext;
        }
    }

可以看到,两个对象的比较,我们只是让它们的List字段里边的元素顺序不一致而已,其他的都一致,判断是否相等的结果是true,符合我们预期的结果。

最后

本代码已经上传到github上了,有需要的同学可以自行下载查阅

https://github.com/larger5/SpringBootComparer