zoukankan      html  css  js  c++  java
  • 非常好用的三款Java Stream API扩展库

    前言

    在Java8中引入的流API可能仍然是Java在过去几年中包含的最重要的新特性。我认为每个Java开发人员在其职业生涯中都有机会使用JAVA STRAM API。或者我更愿意说,你可能每天都在使用它。但是,如果将函数式编程的内置特性与其他一些语言(例如Kotlin)进行比较,您会很快意识到streamapi提供的方法数量非常有限。因此,社区创建了几个库,这些库仅用于扩展纯Java提供的API。今天,我将展示三个流行库提供的最有趣的Java流API扩展:StreamEx、jOOλ 还有Guava。

    依赖

    下面是本文中比较的三个库的当前版本列表。

    <dependencies>
       <dependency>
          <groupId>one.util</groupId>
          <artifactId>streamex</artifactId>
          <version>0.7.0</version>
       </dependency>
       <dependency>
          <groupId>org.jooq</groupId>
          <artifactId>jool</artifactId>
          <version>0.9.13</version>
       </dependency>
       <dependency>
          <groupId>com.google.guava</groupId>
          <artifactId>guava</artifactId>
          <version>28.1-jre</version>
       </dependency>
    </dependencies>

    使用Java Stream扩展压缩流

    在更高级的应用程序中使用Java Streams时,您通常会处理多个流。而且它们通常可以包含不同的对象。在这种情况下,有用的操作之一就是压缩。压缩操作将返回一个流,该流在给定的两个流中包含一对对应的元素,这意味着它们在这些流中的相同位置。让我们考虑两个对象Person和PersonAddress。假设我们有两个流,第一个流仅包含Person对象,第二个带PersonAddress对象,并且元素的顺序清楚地表明了它们的关联,我们可以压缩它们以创建一个新的对象流,其中包含来自Person和的所有字段PersonAddress。这是说明所描述方案的屏幕。

    合并压缩
    合并压缩
    • guava使用

      当前描述的所有三个库均支持压缩。让我们从Guava示例开始。它提供了唯一专用于zip的zip方法-静态方法,该方法具有三个参数:第一流,第二流和映射功能。

      Stream<Person> s1 = Stream.of(
         new Person(1, "John""Smith"),
         new Person(2, "Tom""Hamilton"),
         new Person(3, "Paul""Walker")
      );
      Stream<PersonAddress> s2 = Stream.of(
         new PersonAddress(1, "London""Street1""100"),
         new PersonAddress(2, "Manchester""Street1""101"),
         new PersonAddress(3, "London""Street2""200")
      );
      Stream<PersonDTO> s3 = Streams.zip(s1, s2, (p, pa) -> PersonDTO.builder()
         .id(p.getId())
         .firstName(p.getFirstName())
         .lastName(p.getLastName())
         .city(pa.getCity())
         .street(pa.getStreet())
         .houseNo(pa.getHouseNo()).build());
      s3.forEach(dto -> {
         Assertions.assertNotNull(dto.getId());
         Assertions.assertNotNull(dto.getFirstName());
         Assertions.assertNotNull(dto.getCity());
      });
    • StreamEx

      StreamEx提供比guava上更多的可能性。我们可以在给定的流上调用某些静态方法或非静态方法。让我们看一下如何使用StreamExzipWith方法执行它。

      StreamEx<Person> s1 = StreamEx.of(
         new Person(1, "John""Smith"),
         new Person(2, "Tom""Hamilton"),
         new Person(3, "Paul""Walker")
      );
      StreamEx<PersonAddress> s2 = StreamEx.of(
         new PersonAddress(1, "London""Street1""100"),
         new PersonAddress(2, "Manchester""Street1""101"),
         new PersonAddress(3, "London""Street2""200")
      );
      StreamEx<PersonDTO> s3 = s1.zipWith(s2, (p, pa) -> PersonDTO.builder()
         .id(p.getId())
         .firstName(p.getFirstName())
         .lastName(p.getLastName())
         .city(pa.getCity())
         .street(pa.getStreet())
         .houseNo(pa.getHouseNo()).build());
      s3.forEach(dto -> {
         Assertions.assertNotNull(dto.getId());
         Assertions.assertNotNull(dto.getFirstName());
         Assertions.assertNotNull(dto.getCity());
      });
    • jOOλ 使用

      这个例子几乎是相同的。我们有一个zip在给定流上调用的方法。

      Seq<Person> s1 = Seq.of(
      new Person(1, "John""Smith"),
      new Person(2, "Tom""Hamilton"),
      new Person(3, "Paul""Walker"));
      Seq<PersonAddress> s2 = Seq.of(
         new PersonAddress(1, "London""Street1""100"),
         new PersonAddress(2, "Manchester""Street1""101"),
         new PersonAddress(3, "London""Street2""200"));
      Seq<PersonDTO> s3 = s1.zip(s2, (p, pa) -> PersonDTO.builder()
         .id(p.getId())
         .firstName(p.getFirstName())
         .lastName(p.getLastName())
         .city(pa.getCity())
         .street(pa.getStreet())
         .houseNo(pa.getHouseNo()).build());
      s3.forEach(dto -> {
         Assertions.assertNotNull(dto.getId());
         Assertions.assertNotNull(dto.getFirstName());
         Assertions.assertNotNull(dto.getCity());
      });

    Join扩展

    压缩操作根据两个不同流中的元素在这些流中的顺序来合并它们。如果我们想根据元素的字段(例如id)而不是流中的顺序关联元素,该怎么办。两个实体之间的LEFT JOIN或RIGHT JOIN之类的东西。操作的结果应与上一部分相同–包含来自Person和的所有字段的新对象流PersonAddress。下图说明了所描述的操作。

    当涉及联接操作时,只有jOOλ提供了一些方法。由于它专用于面向对象的查询,因此我们可以在许多联接选项之间进行选择。例如有innerJoin,leftOuterJoin,rightOuterJoin和crossJoin方法。在下面可见的源代码中,您可以看到一个示例innerJoin用法。此方法采用两个参数:要加入的流和谓词以匹配来自第一个流和加入流的元素。如果要基于innerJoin结果创建新对象,则应另外调用mapoperation。

    Seq<Person> s1 = Seq.of(
          new Person(1, "John""Smith"),
          new Person(2, "Tom""Hamilton"),
          new Person(3, "Paul""Walker"));
    Seq<PersonAddress> s2 = Seq.of(
          new PersonAddress(2, "London""Street1""100"),
          new PersonAddress(3, "Manchester""Street1""101"),
          new PersonAddress(1, "London""Street2""200"));
    Seq<PersonDTO> s3 = s1.innerJoin(s2, (p, pa) -> p.getId().equals(pa.getId())).map(t -> PersonDTO.builder()
          .id(t.v1.getId())
          .firstName(t.v1.getFirstName())
          .lastName(t.v1.getLastName())
          .city(t.v2.getCity())
          .street(t.v2.getStreet())
          .houseNo(t.v2.getHouseNo()).build());
    s3.forEach(dto -> {
       Assertions.assertNotNull(dto.getId());
       Assertions.assertNotNull(dto.getFirstName());
       Assertions.assertNotNull(dto.getCity());
    });

    Stream扩展分组

    Java流API仅通过Java.util.Stream.Collectors中的静态方法groupingBy支持的下一个有用操作是分组(s1.collect(Collectors.groupingBy(PersonDTO::getCity)))。在流上执行这样一个操作的结果是,您得到一个带有键的映射,这些键是将分组函数应用于输入元素后得到的值,其对应的值是包含输入元素的列表。这个操作是某种聚合,因此得到java.util.List,结果是没有java.util.stream.stream。 StreamEx和jOOλ 提供一些分组流的方法。让我们从StreamEx groupingBy操作示例开始。假设我们有一个PersonDTO对象的输入流,我们将按个人的家乡城市对它们进行分组。

    • StreamEx 使用

      StreamEx<PersonDTO> s1 = StreamEx.of(
         PersonDTO.builder().id(1).firstName("John").lastName("Smith").city("London").street("Street1").houseNo("100").build(),
         PersonDTO.builder().id(2).firstName("Tom").lastName("Hamilton").city("Manchester").street("Street1").houseNo("101").build(),
         PersonDTO.builder().id(3).firstName("Paul").lastName("Walker").city("London").street("Street2").houseNo("200").build(),
         PersonDTO.builder().id(4).firstName("Joan").lastName("Collins").city("Manchester").street("Street2").houseNo("201").build()
      );
      Map<String, List<PersonDTO>> m = s1.groupingBy(PersonDTO::getCity);
      Assertions.assertNotNull(m.get("London"));
      Assertions.assertTrue(m.get("London").size() == 2);
      Assertions.assertNotNull(m.get("Manchester"));
      Assertions.assertTrue(m.get("Manchester").size() == 2);
    • jOOλ 使用

      Seq<PersonDTO> s1 = Seq.of(
            PersonDTO.builder().id(1).firstName("John").lastName("Smith").city("London").street("Street1").houseNo("100").build(),
            PersonDTO.builder().id(2).firstName("Tom").lastName("Hamilton").city("Manchester").street("Street1").houseNo("101").build(),
            PersonDTO.builder().id(3).firstName("Paul").lastName("Walker").city("London").street("Street2").houseNo("200").build(),
            PersonDTO.builder().id(4).firstName("Joan").lastName("Collins").city("Manchester").street("Street2").houseNo("201").build()
      );
      Map<String, List<PersonDTO>> m = s1.groupBy(PersonDTO::getCity);
      Assertions.assertNotNull(m.get("London"));
      Assertions.assertTrue(m.get("London").size() == 2);
      Assertions.assertNotNull(m.get("Manchester"));
      Assertions.assertTrue(m.get("Manchester").size() == 2);

    多重串联

    这是一个非常简单的场景。javastreamapi提供了一种用于连接的静态方法,但只适用于两个流。有时在一个步骤中浓缩多个流是很方便的。guava跟jOOλ为此提供专用方法。

    • 下面是用jOOλ调用concat方法的示例

      Seq<Integer> s1 = Seq.of(1, 2, 3);
      Seq<Integer> s2 = Seq.of(4, 5, 6);
      Seq<Integer> s3 = Seq.of(7, 8, 9);
      Seq<Integer> s4 = Seq.concat(s1, s2, s3);
      Assertions.assertEquals(9, s4.count());
    • guava方法的示例

      Stream<Integer> s1 = Stream.of(1, 2, 3);
      Stream<Integer> s2 = Stream.of(4, 5, 6);
      Stream<Integer> s3 = Stream.of(7, 8, 9);
      Stream<Integer> s4 = Streams.concat(s1, s2, s3);
      Assertions.assertEquals(9, s4.count());

    分区扩展

    分区操作与分组非常相似,但将输入流分为两个列表或流,其中第一个列表中的元素满足给定的谓词,而第二个列表中的元素则不满足。

    • StreamEx partitioning by方法将返回地图中的两个列表对象。

      StreamEx<PersonDTO> s1 = StreamEx.of(
            PersonDTO.builder().id(1).firstName("John").lastName("Smith").city("London").street("Street1").houseNo("100").build(),
            PersonDTO.builder().id(2).firstName("Tom").lastName("Hamilton").city("Manchester").street("Street1").houseNo("101").build(),
            PersonDTO.builder().id(3).firstName("Paul").lastName("Walker").city("London").street("Street2").houseNo("200").build(),
            PersonDTO.builder().id(4).firstName("Joan").lastName("Collins").city("Manchester").street("Street2").houseNo("201").build()
      );
      Map<Boolean, List<PersonDTO>> m = s1.partitioningBy(dto -> dto.getStreet().equals("Street1"));
      Assertions.assertTrue(m.get(true).size() == 2);
      Assertions.assertTrue(m.get(false).size() == 2);
    • 与StreamEx相反,jOOλ 在Tuple2对象内返回两个流(Seq)。与StreamEx相比,这种方法有一个很大的优势—您仍然可以在不进行任何转换的情况下对结果调用流操作。

       Seq<PersonDTO> s1 = Seq.of(
           PersonDTO.builder().id(1).firstName("John").lastName("Smith").city("London").street("Street1").houseNo("100").build(),
           PersonDTO.builder().id(2).firstName("Tom").lastName("Hamilton").city("Manchester").street("Street1").houseNo("101").build(),
           PersonDTO.builder().id(3).firstName("Paul").lastName("Walker").city("London").street("Street2").houseNo("200").build(),
           PersonDTO.builder().id(4).firstName("Joan").lastName("Collins").city("Manchester").street("Street2").houseNo("201").build());
       Tuple2<Seq<PersonDTO>, Seq<PersonDTO>> t = s1.partition(dto -> dto.getStreet().equals("Street1"));
       Assertions.assertTrue(t.v1.count() == 2);
       Assertions.assertTrue(t.v2.count() == 2);

    汇总扩展

    只有jOOλ提供一些流聚合方法。例如,我们可以计算总和、平均值或中位数。自从乔λ是jOOQ的一部分,它的目标是用于面向对象的查询,实际上,它提供了许多与sqlselect子句相对应的操作。

    下面可见的源代码片段以所有人的年龄为例,说明了我们如何轻松地计算对象流中选定字段的总和。

    Seq<Person> s1 = Seq.of(
       new Person(1, "John""Smith", 35),
       new Person(2, "Tom""Hamilton", 45),
       new Person(3, "Paul""Walker", 20)
    );
    Optional<Integer> sum = s1.sum(Person::getAge);
    Assertions.assertEquals(100, sum.get());

    配对扩展

    StreamEx允许您处理流中的相邻对象对,并对它们应用给定的函数。它可以通过使用pairMap函数来实现。在下面可见的代码片段中,我计算流中每对相邻数字的和。

    StreamEx<Integer> s1 = StreamEx.of(1, 2, 1, 2, 1);
    StreamEx<Integer> s2 = s1.pairMap(Integer::sum);
    s2.forEach(i -> Assertions.assertEquals(3, i));

    END

    欢迎关注公众号! 公众号回复:入群 ,扫码加入我们交流群! 扫码关注公众号获取更多学习资料

    阅读更多文章

    欢迎关注我的公众号!里面可以加入微信技术交流群!
  • 相关阅读:
    修改Oracle数据库的字符集为UTF-8
    yum源的更新问题
    Spark的编译
    hadoop版本和位数的查看方法
    hadoop2.x通过Zookeeper来实现namenode的HA方案以及ResourceManager单点故障的解决方案
    zookeeper3.4.6的安装
    SQL SERVER回滚恢复误操作的数据
    SQLServer异常捕获
    111111
    sql分割函数
  • 原文地址:https://www.cnblogs.com/1ssqq1lxr/p/14736209.html
Copyright © 2011-2022 走看看