【进阶篇】利用 Stream 流对比两个集合的常用操作分享 ...

打印 上一主题 下一主题

主题 918|帖子 918|积分 2754

目录

前言

在之前的开发中,遇到了这样的需求:记录某个更新操作之前的数据作为日志内容,之后可以供管理员在页面上查看该日志。
思路:

  • 更新接口拿入参与现在数据库该条数据逐一对比,将不同的部分取出;
  • 在更新操作前取出现在数据库的该条数据,更新操作后再取出同一条数据,比力两者的异同。
经过短暂对比后,我选择方案2,理由如下:

  • 前端入参未经过后端真实性校验,即万一进来的不是同一条数据呢?这样是不可靠的。
  • 后端先拿参数去数据库找,如果有这条数据,那么拿出来做对比可以保证更新的是同一条数据。
要点:

  • 从数据库里拿出来的一条数据其实是个实体类对象,那是否可以两个对象逐一比力属性值是否相等呢?这个不现实,因为引用类型的对象在内存中的地址肯定不同,以是对象 .equals() 的结果永远是 false;
  • 既然对象不能直接比力,那么就将其先转换为一个集合后再进行 Stream 操作;
  • 这里必要比力的两个集合的元素属性名相同,但是值不一定相同;
一、集合的比力

详细情况可以分为:1、是否必要得到一个新的流?2、是否只必要一个简朴 boolean 结果?
我开发需求是要得到详细哪些数据不一样,以是选择返回一个新的流,只是得到一个 boolean 来判定是否相同是不敷的。
1.1必要得到一个新的流


  • 如果是得到一个新的流,那么推荐利用.filter() + .collect()
    1.     @Test
    2.     public void testFilter(){
    3.         //第一个数组
    4.         List<ListData> list1 = new ArrayList<>();
    5.         list1.add(new ListData("测测名字11",11,"email@11"));
    6.         list1.add(new ListData("测测名字22",22,"email@22"));
    7.         list1.add(new ListData("测测名字33",33,"email@33"));
    8.         log.info("第一个数组为:{}", list1);
    9.         //第二个数组
    10.         List<ListData> list2 = new ArrayList<>();
    11.         list2.add(new ListData("测测名字111",111,"email@11"));
    12.         list2.add(new ListData("测测名字22",22,"email@22"));
    13.         list2.add(new ListData("测测名字33",33,"email@33"));
    14.         log.info("第二个数组为:{}", list2);
    15.         //返回一个新的结果数组
    16.         List<ListData> resultList = list1.stream()
    17.             //最外层的filter里是条件,这个条件需要返回一个boolean:符合条件返回true,不符合条件返回false
    18.             .filter(p1 -> list2.stream()
    19.                     //这个filter也是条件:判断两个数组里名字和年龄是否都相等,符合条件返回true,不符合条件返回false
    20.                     .filter(p2 -> p2.getName().equals(p1.getName()) && p2.getAge().equals(p1.getAge()))
    21.                     //如有内容则返回流中的第一条记录,其它情况都返回空
    22.                     .findFirst().orElse(null)
    23.                     //这个是最外层的filter的断言
    24.                     == null)
    25.             //将上一步流处理的的结果,收集成一个新的集合
    26.             .collect(Collectors.toList());
    27.         log.info("经过 Stream 流处理后输出的结果数组为: {}", resultList);
    28.     }
    复制代码
    团结.filter() + noneMatch() 其实也与上面的语句效果相同:
    1.        List<ListData> resultList = list1.stream()
    2.                .filter(p1 -> list2.stream()
    3.                         //这个 noneMatch 也是条件:判断两个数组里名字和年龄是否都相等,符合条件返回true,不符合条件返回false
    4.                         .noneMatch(p2 -> p2.getName().equals(p1.getName()) && p2.getAge().equals(p1.getAge())))
    5.                 .collect(Collectors.toList());
    6.        log.info("经过 Stream 流处理后输出的结果数组为: {}", resultList);
    复制代码
    团结 filter() + contains() 方法( 其中 contains() 方法的利用详见 1.2 小节的留意事项),与以上的效果也一样:
    1.       List<ListData> resultList = list1.stream().filter(p1 -> !list2.contains(p1)).collect(Collectors.toList());
    2.       log.info("经过 Stream 流处理后输出的结果数组为: {}", resultList);
    复制代码
    下面是以上代码的运行结果如图 1 所示:
图11.2只必要一个简朴 boolean 结果


  • 如果只必要一个简朴的 boolean 结果,那么推荐利用.anyMatch() 大概 allMatch()
    1.         //返回一个boolean结果
    2.         boolean flag = list1.stream()
    3.                 //只要流中任意一个元素符合条件则返回true,否则返回false
    4.                 .anyMatch(p1 -> list2.stream()
    5.                         //如果流中全部元素都符合条件,就返回true,否则返回false;当流为空时总是返回true
    6.                         .allMatch(p2 -> p2.getName().equals(p1.getName()) && p2.getAge().equals(p1.getAge())));
    7.         log.info("经过 Stream 流对比是否相等: {}", flag);
    复制代码
    下面是以上代码的运行结果如图 2 所示:
    图2
  • 除了 Stream 流之外,还可以利用 JDK 自带的.contains() 相干方法来判定
    1. //List 集合接口自带的方法
    2. boolean isEqual = list1.containsAll(list2) && list2.containsAll(list1);
    复制代码
    1. //与上述方法效果一致
    2. boolean isEqual = list1.stream().anyMatch(p1 -> list2.contains(p1));
    3. //下面的是上述语句的 lambda 表达式写法
    4. //boolean isEqual = list1.stream().anyMatch(list2::contains);
    复制代码
    留意事项:.contains() 相干方法底层是迭代器 Iterator 以及 .equals() 方法,必要为 List 集合包罗的泛型  中重写.equals() 方法才能利用,举例如下所示:
    1. @Data
    2. @AllArgsConstructor
    3. @NoArgsConstructor
    4. public class ListData {
    5.     private String name;
    6.     private Integer age;
    7.     private String email;
    8.     @Override
    9.     public boolean equals(Object o) {
    10.         if (this == o) return true;
    11.         if (o == null || getClass() != o.getClass()) return false;
    12.         ListData listData = (ListData) o;
    13.         return Objects.equals(name, listData.name) && Objects.equals(age, listData.age) && Objects.equals(email, listData.email);
    14.     }
    15. }
    复制代码
    下面是以上代码的运行结果如图 3 所示:
    图3
  • 理论上可以用 for 循环大概迭代器来做,效果与利用 .containsAll() 方法差不多,但是自己手写的话可能会比力复杂,数据量稍大些的话效率较低,一样寻常不思量采用,这里我就不演示了。
二、简朴集合的对比

上述的集合都是泛型为自界说引用类型的集合,下面分享一些简朴集合,如整形、字符串类型集合的 Stream 流对比操作。
2.1整型元素集合
  1.         List<Integer> list1 = Arrays.asList(1, 6,);
  2.         List<Integer> list2 = Arrays.asList(3, 2, 1);
  3.         //Java 本身提供的 Integer 类已经实现了 Comparable 接口,可以直接.sort() 比较
  4.         boolean isEqual = list1.stream().sorted().collect(Collectors.toList())
  5.             .equals(list2.stream().sorted().collect(Collectors.toList()));
  6.         log.info("是否相等:{}", isEqual);
复制代码
2.2字符串元素集合
  1.         // 先排序然后转成 String 逗号分隔,joining()拼接
  2.         List<String> list3 = Arrays.asList("语文","数学","英语");
  3.         List<String> list4 = Arrays.asList("数学","英语","语文");
  4.         //Java 本身提供的 String 类也已经实现了 Comparable 接口
  5.         boolean flag = list3.stream().sorted().collect(Collectors.toList())
  6.             .equals(list4.stream().sorted().collect(Collectors.toList()));
  7.         log.info("是否相等:{}", flag);
复制代码
下面是简朴集合比力的运行结果,如图 4 所示:
   
  图42.3其它比力

不知道大家有没有发现,上述简朴类型的类可以直接比力,而自己写的类就不能,会报”cannot be cast to java.lang.Comparable“。
举个例子,对于自界说的引用类型 ListData , Java 不知道应该怎样为 ListData 的对象排序,是应该按名字排序? 还是按年龄来排序?
留意:.sort() 方法底层实现必要依赖 Comparator 接口,那么这个引用类型 ListData 类要自己手动去实现 Comparator() 接口并重写 compare() 方法才能这样做比力。
  1.         List<ListData> list1 = new ArrayList<>();
  2.         list1.add(new ListData("泛型为引用类型", 666, "abc"));
  3.         List<ListData> list2 = new ArrayList<>();
  4.         list2.add(new ListData("泛型为引用类型", 888, "def"));
  5.         //这里想要收集成为集合进行比较,需要先根据特定的元素排序(年龄),然后再按顺序比较
  6.         boolean flag = list1.stream().sorted(Comparator.comparing(ListData::getAge)).collect(Collectors.toList())
  7.                 .equals(list2.stream().sorted(Comparator.comparing(ListData::getAge)).collect(Collectors.toList()));
  8.         log.info("是否相等: {}", flag);
复制代码
三、Stream 基础回顾

Stream API 是 Java 8 中最为紧张的更新之一,是处置惩罚集合的关键抽象概念,也是每个 Java 后端开发人员都必须无条件掌握的内容。
Stream 和 Collection 集合的主要区别:Collection 是内存数据结构,重在数据的存储;而 Stream 是集合的操作计算,重在一系列的流式操作。
3.1基本概念


  • Stream 不会自己存储元素,会返回一个持有结果的新的流;
  • Stream 操作是延迟执行的,即一旦执行停止操作,就执行中心操作链,并产生结果;
  • Stream 一旦执行了停止操作,那么就不能再执行中心操作大概其它停止操作。
3.2 Stream 操作的三个步调

3.2.1创建 Stream

一个数据源(如:集合、数组)来获取一个流,详细有 3 种方式来创建:

  • 通过集合直接创建(最常用)
    1. //Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
    2. //返回一个顺序流
    3. default Stream<E> stream(){}
    4. //返回一个并行流
    5. default Stream<E> parallelStream{}
    复制代码
  • Arrays 也可以获取数组流
    1. //返回一个流
    2. public static <T> Stream<T> stream(T[] array){}
    复制代码
  • 调用 Stream 类静态方法 of() 来创建流
    1. public static<T> Stream<T> of(T... values){}
    复制代码
3.2.2中心操作

每次处置惩罚都会返回一个持有结果的新 Stream,即中心操作的方法返回值仍旧是 Stream 类型的对象。因此中心操作可以是链式的,可对数据源的数据进行 n 次处置惩罚,但是在停止操作前,并不会真正执行;
中心操作可谓是最紧张也最常利用的操作,详细分为3种:筛选与切片、映射、排序,如以下表格所示:
<ul>筛选与切片
[table][tr]方法描 述[/tr][tr][td]Stream filter(Predicate

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

反转基因福娃

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表