乐趣区

关于java:java8函数式编程之Stream流处理的方法和案例讲解

函数式编程最早是数学家阿隆佐·邱奇钻研的一套函数变换逻辑,又称 Lambda Calculus(λ-Calculus),所以也常常把函数式编程称为 Lambda 计算。

为什么 Java 须要 Lambda 表达式进行函数式编程呢?在我看来,有以下益处:

  • 1、面向对象编程是对数据进行形象,而函数式编程是对行为进行形象;
  • 2、减少 Lambda 表达式,让代码在多核服务器上更高效运行;
  • 3、始终以来,Java 始终被诟病的中央,就是代码写得比拟啰嗦,简略的 CURD 都要写好几行代码,Java8 推出函数式编程后,啰嗦且简短的代码失去极大改观,用 Java 也能写出简洁柔美的代码。

不多啰嗦,上面开始函数式编程之 Stream 流解决的办法和案例解说。

1. 引言

Streams API 已在 Java 8 中引入,并且曾经是 Java 语言标准的一部分多年了。尽管如此,平时的工作中,还是有很多我的项目还停留在 java1.7 中的应用中,而且 java8 的很多新个性都是革命性的,尤其是 stream 流解决。因而,这篇文章里,将介绍 Streams 的基本概念,并通过一些示例对其进行解释。

本文参考自官网 Java 文档 java-stream 流解决。与汇合相比,Streams 的一些特色(取自官网文档)如下:

  • 流不存储数据。相同,它们是数据的起源。
  • 流实质上是功能性的,这可能就是为什么流对大多数人来说很难了解的起因。它们产生一个后果,例如,像一个总和或一个新的流。
  • 流的许多操作都是惰性求值的(laziness-seeking)。操作能够是两头操作,也能够是终止操作。咱们将在下一节中介绍这两者。惰性求值是指该操作只是对 stream 的一个形容,并不会马上执行。这类惰性的操作在 stream 中被称为两头操作(intermediate operations)。例如,查找流中元素的第一个匹配项。不用查看流的所有元素。找到第一个匹配项后,能够完结搜寻。
  • 可能是有限的。只有流生成数据,流就不会完结。
  • 流的元素只能拜访一次,和迭代器 Iterator 类似,当须要反复拜访某个元素时,须要从新生成一个新的 stream。

在接下来的局部中,咱们将介绍如何创立 Streams,介绍一些两头操作,最初咱们将介绍一些终止操作。这些源代码能够在结尾的 github 源码里提供,不须要本人复制粘贴。

2. 创立流源

有几种办法能够创立流。它们能够从汇合,数组,文件等创立。在本节中,咱们将创立一些测试,向您展现如何以不同的形式创立流。咱们将创立并应用终止操作,以便将 生产到 . 咱们不对 执行任何其余操作,咱们将它留给其余局部。最初,咱们断言是否与咱们冀望的雷同。测试位于单元测试中。

2.1 从汇合创立流

应用 java.util.Collection.stream() 办法

private static final List<String> stringsList = Arrays.asList("a", "b", "c");

@Test
public void createStreamsFromCollection() {List<String> streamedStrings = stringsList.stream().collect(Collectors.toList());
    assertLinesMatch(stringsList, streamedStrings);
}

2.2 从数组创立流

应用 java.util.Arrays.stream(T[]array)办法

@Test
public void createStreamsFromArrays() {List<String> streamedStrings = Arrays.stream(new String[]{"a", "b", "c"}).collect(Collectors.toList());
    assertLinesMatch(stringsList, streamedStrings);
}

2.3 从流创立流。

应用 Stream 的静态方法:of()、iterate()、generate()

@Test
public void createStreamsFromStreamOf() {List<String> streamedStrings = Stream.of("a", "b", "c").collect(Collectors.toList());
    assertLinesMatch(stringsList, streamedStrings);
}

2.4 从 IntStream 创立流

应用将 int 值作为参数来创立。

@Test
public void createStreamsFromIntStream() {int[] streamedInts = IntStream.of(1, 2, 3).toArray();
    assertArrayEquals(new int[]{1, 2, 3}, streamedInts);
}

2.5 从文件创建流

从文件中读取进行创立。

    @Test
    public void createStreamsFromFile() {
        try {List<String> expectedLines = Arrays.asList("file1", "file2", "file3","file4");
            BufferedReader reader = new BufferedReader(new FileReader(new File(System.getProperty("user.dir")+"/src/main/resources/file.txt")));
            List<String> streamedLines = reader.lines().collect(Collectors.toList());
            assertLinesMatch(expectedLines, streamedLines);
        } catch (FileNotFoundException e) {e.printStackTrace();
        }
    }

3. 两头操作(intermediate operations)

两头操作(intermediate operations)指的是将一个 stream 转换为另一个 stream 的操作,譬如 filter 和 map 操作。这些操作返回新的,但不返回最终后果。两头操作能够分为无状态操作(不保留无关先前解决的元素的信息)和有状态操作(可能须要在生成两头后果之前解决所有元素)。

能够在 Streams API 中找到可调用的操作的残缺列表。

public class Shoe {
    private int id;
    private String brand;
    private String size;
    private String color;

    public Shoe(int id, String brand, String size, String color) {
        this.id = id;
        this.brand = brand;
        this.size = size;
        this.color = color;
    }

    ...

}

在单元测试中,咱们定义了四个对象,咱们将在下一个示例中应用:shoe

    private static final Shoe volkswagenGolf = new Shoe(0, "Volkswagen", "L", "blue");
    private static final Shoe skodaOctavia = new Shoe(1, "Skoda", "XL", "green");
    private static final Shoe renaultKadjar = new Shoe(2, "Renault", "XXL", "red");
    private static final Shoe volkswagenTiguan = new Shoe(3, "Volkswagen", "M", "red");

3.1 无状态操作

3.1.1 filter 操作

该操作容许咱们基于给定的. 在示例中,咱们首先创立 4 双鞋子中的一双,而后创立一双仅蕴含 Volkswagen 鞋子的新鞋子。

@Test
public void filterStream() {List<Shoe> expectedShoes = Arrays.asList(volkswagenGolf, volkswagenTiguan);
    List<Shoe> filteredShoes = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                                   .filter(shoe -> shoe.getBrand().equals("Volkswagen"))
                                   .collect(Collectors.toList());
    assertIterableEquals(expectedShoes, filteredShoes);
}
3.1.2 map 操作

在后面的所有示例中,源中的类型和后果始终雷同。通常,您心愿对每个元素利用一个函数。例如,如果咱们心愿后果仅蕴含品牌而不是对象,咱们能够应用该操作并将该办法利用于每个元素,从而产生仅具备品牌名称的新元素。

@Test
public void mapStream() {List<String> expectedBrands = Arrays.asList("Volkswagen", "Skoda", "Renault", "Volkswagen");
    List<String> brands = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                                .map(Shoe::getBrand)
                                .collect(Collectors.toList());
    assertIterableEquals(expectedBrands, brands);
}
3.1.3 组合过滤和映射操作 (filter+map)

当然,将操作组合并将它们链接在管道中是齐全无效的。在下一个示例中,咱们进行筛选以检索 Volkswagen 鞋子,而后应用该操作仅检索色彩。这样,咱们最终会失去一个蕴含 Volkswagen 鞋子色彩的列表。

@Test
public void filterMapStream() {List<String> expectedColors = Arrays.asList("blue", "red");
    List<String> volkswagenColors = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                                          .filter(shoe -> shoe.getBrand().equals("Volkswagen"))
                                          .map(Shoe::getColor)
                                          .collect(Collectors.toList());
    assertIterableEquals(expectedColors, volkswagenColors);
}

3.2 有状态操作

3.2.1 distinct 操作

不同操作将返回仅蕴含基于元素的办法实现的不同元素的。在下一个示例中,咱们首先检索一个品牌,而后对其执行操作。这就产生了咱们应用的三个不同品牌的列表。

@Test
public void distinctStream() {List<String> expectedBrands = Arrays.asList("Volkswagen", "Skoda", "Renault");
    List<String> brands = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                                .map(Shoe::getBrand)
                                .distinct()
                                .collect(Collectors.toList());
    assertIterableEquals(expectedBrands, brands);
}
3.2.2 sorted 操作

该操作将依据其天然程序对元素进行排序。也能够应用 a 作为参数,以便进行更自定义的排序。下一个示例将从 中检索品牌并按字母程序对其进行排序。

@Test
public void sortedStream() {List<String> expectedSortedBrands = Arrays.asList("Renault", "Skoda", "Volkswagen", "Volkswagen");
    List<String> brands = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                                .map(Shoe::getBrand)
                                .sorted()
                                .collect(Collectors.toList());
    assertIterableEquals(expectedSortedBrands, brands);
}

3.3 用于调试目标的 peek

咱们将探讨的最初一个两头操作是操作。这是一个非凡的,次要用于调试目标。将在应用元素时对其执行操作。让咱们以组合的过滤器和地图为例,并向其增加一些操作,以便打印正在应用的元素。

@Test
public void peekStream() {List<String> expectedColors = Arrays.asList("blue", "red");
    List<String> volkswagenColors = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                                          .filter(shoe -> shoe.getBrand().equals("Volkswagen"))
                                          .peek(e -> System.out.println("Filtered value:" + e))
                                          .map(Shoe::getColor)
                                          .peek(e -> System.out.println("Mapped value:" + e))
                                          .collect(Collectors.toList());
    assertIterableEquals(expectedColors, volkswagenColors);
}

此测试的输入为:

Filtered value: Shoe{id=0, brand='Volkswagen', type='Golf', color='blue'}

Mapped value: blue

Filtered value: Shoe{id=3, brand='Volkswagen', type='Tiguan', color='red'}

Mapped value: red

4. 终止操作(terminal operations)

终止操作(terminal operations)则指的是那些会产生一个新值或副作用(side-effect)的操作,例如 count 和 forEach 操作。

4.1 collecti 操作

咱们曾经在后面的所有示例中应用了终止操作。咱们总是在操作中应用参数。不过还有更多选项能够应用。咱们将在下一个示例中介绍一些内容。残缺的列表能够在 JavaDoc 中找到。

办法 collect 里应用,咱们能够应用办法 Collectors.joining 进行分隔符分隔。

@Test
public void collectJoinStream() {
    String expectedBrands = "Volkswagen;Skoda;Renault;Volkswagen";
    String joinedBrands = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                                .map(Shoe::getBrand)
                                .collect(Collectors.joining(";"));
    assertEquals(expectedBrands, joinedBrands);
}

Collectors.summingInt应用,咱们能够计算元素的属性之和。在咱们的示例中,咱们只计算鞋子 id 的总和。

@Test
public void collectSummingIntStream() {int sumIds = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                       .collect(Collectors.summingInt(Shoe::getId));
   assertEquals(6, sumIds);
}

Collectors.groupingBy应用,咱们能够将元素分组到 . 在咱们的示例中,咱们依据品牌对元素进行分组。

@Test
public void collectGroupingByStream() {Map<String, List<Shoe>> expectedShoes = new HashMap<>();
    expectedShoes.put("Skoda", Arrays.asList(skodaOctavia));
    expectedShoes.put("Renault", Arrays.asList(renaultKadjar));
    expectedShoes.put("Volkswagen", Arrays.asList(volkswagenGolf, volkswagenTiguan));
 
    Map<String, List<Shoe>> groupedShoes = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                                               .collect(Collectors.groupingBy(Shoe::getBrand));
    assertTrue(expectedShoes.equals(groupedShoes));
}

4.2 reduce 操作

reduce,该操作对 的元素执行减量。它应用恒等式(即起始值)和执行归约的累积函数。在咱们的示例中,咱们对元素的 id 执行求和,就像咱们应用该操作时所做的那样。

@Test
public void reduceStream() {int sumIds = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                       .map(Shoe::getId)
                       .reduce(0, Integer::sum);
    assertEquals(6, sumIds);
}

4.3 forEach 操作

该操作对每个元素执行一个函数。它与两头操作十分类似。

@Test
public void forEachStream() {Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
          .forEach(System.out::println);
}

该测试的后果如下:

Shoe{id=0, brand='Volkswagen', type='Golf', color='blue'}

Shoe{id=1, brand='Skoda', type='Octavia', color='green'}

Shoe{id=2, brand='Renault', type='Kadjar', color='red'}

Shoe{id=3, brand='Volkswagen', type='Tiguan', color='red'}

4.4 count 操作

该操作对流中的元素数进行计数。

@Test
public void countStream() {long count = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).count();
    assertEquals(4, count);
}

4.5 max 操作

该操作返回基于给定的 的最大元素。在咱们的示例中,咱们创立一个 id 并检索具备最高 id 的元素。请留神,这将返回一个 . 当无奈确定最高 id 时,咱们只是提供了一个代替值。

@Test
public void maxStream() {int maxId = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan)
                      .map(Shoe::getId)
                      .max((o1, o2) -> o1.compareTo(o2))
                      .orElse(-1);
    assertEquals(3, maxId);
}

5. 论断

Java Streams API 十分弱小,学习起来并不难。stream 流解决可读性还是很不错的,想让本人代码变得更加简洁好看,那就来跟着本文学习吧,还有源码跟着调试。
如果您还不相熟 Streams API,能够装置 IDEA 的插件工具,调试 Java 流的杰出 IntelliJ 性能。

本文案例的源码地址:beierzsj/javaStreamDemo (github.com)

参考文档

  • java-stream 流解决
  • 汇合
  • Streams API
  • JavaDoc
  • 调试 Java 流的 IDEA 工具装置插件

博主集体博客网站:奇想派

本文原创作者:奇想派、一名致力分享的程序员。

文章首发平台:微信公众号【编程达人】

原创不易!各位小伙伴感觉文章不错的话,无妨关注公众号,进行 点赞(在看)、转发 三连走起!谢谢大家!

退出移动版