使用 Streams

一个 Stream 是在其上顺序和并行聚合操作可以执行元素的序列。任何给定的 Stream 都可能有无限量的数据流过它。结果,从 Stream 接收的数据在到达时被单独处理,而不是完全对数据进行批处理。当与 lambda 表达式结合使用时,它们提供了一种使用函数方法对数据序列执行操作的简明方法。

例如:看到它在 Ideone 工作

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .forEach(System.out::println);

输出:

苹果
香蕉
ORANGE
PEAR

上述代码执行的操作可归纳如下:

  1. 使用静态工厂方法 Stream.of(values) 创建一个包含有序 Stream 水果 String 元素的 Stream<String>

  2. 所述 filter() 操作只保留匹配给定的谓词(元件,当由所述谓词返回真测试),该元件。在这种情况下,它保留了包含 a 的元素。谓词以 lambda 表达式给出。

  3. 所述 map() 操作变换使用给定的函数,称为映射器的每个元素。在这种情况下,每个水果 String 使用方法参考 String::toUppercase映射到其大写的 String 版本。

    请注意,如果映射函数返回与其输入参数不同的类型,则 map() 操作将返回具有不同泛型类型的流。例如,在 Stream<String> 上调用 .map(String::isEmpty) 会返回一个 Stream<Boolean>

  4. 所述 sorted() 操作根据其自然顺序排序 Stream 的元素(按字典顺序,在 String 的情况下)。

  5. 最后, forEach(action) 操作执行一个动作,该动作作用于 Stream 的每个元素,并将其传递给 Consumer 。在该示例中,每个元素都被简单地打印到控制台。该操作是终端操作,因此不可能再次对其进行操作。

    注意由于终端操作,执行 Stream 上定义的操作。如果没有终端操作,则不处理流。流不能重用。一旦调用终端操作,Stream 对象就变得不可用。

StackOverflow 文档

操作(如上所示)被链接在一起以形成可以被视为对数据的查询。

关闭流

**请注意,通常不必关闭 Stream。**只需要关闭在 IO 通道上运行的流。大多数 Stream 类型不依赖于资源,因此不需要关闭。

Stream 界面扩展了 AutoCloseable 。可以通过调用 close 方法或使用 try-with-resource 语句来关闭流。

应该关闭 Stream 的示例用例是当你从文件创建线条时:

try (Stream<String> lines = Files.lines(Paths.get("somePath"))) {
    lines.forEach(System.out::println);
}

Stream 接口还声明了 Stream.onClose() 方法,该方法允许你注册 Runnable 处理程序,这些处理程序将在流关闭时调用。一个示例用例是生成流的代码需要知道何时使用它来执行某些清理。

public Stream<String>streamAndDelete(Path path) throws IOException {
    return Files.lines(path).onClose(() -> someClass.deletePath(path));
}

只有在 try-with-resources 语句显式或隐式调用 close() 方法时,才会执行运行处理程序。

处理订单

Stream 对象的处理可以是顺序的或并行的

顺序模式中,元素按照 Stream 的来源顺序处理。如果订购了 Stream(例如 SortedMap 实现或 List ),则保证处理与源的顺序相匹配。但是,在其他情况下,应注意不要依赖于排序(参见: Java HashMap keySet() 迭代顺序是否一致? )。

例:

List<Integer> integerList = Arrays.asList(0, 1, 2, 3, 42); 

// sequential 
long howManyOddNumbers = integerList.stream()
                                    .filter(e -> (e % 2) == 1)
                                    .count(); 

System.out.println(howManyOddNumbers); // Output: 2

住在 Ideone 上

并行模式允许在多个内核上使用多个线程,但不保证元素的处理顺序。

如果在顺序 Stream 上调用多个方法,则不必调用每个方法。例如,如果过滤了 Stream 并且元素的数量减少为 1,则不会发生对诸如 sort 之类的方法的后续调用。这可以提高顺序 Stream 的性能 - 这是并行 Stream 无法实现的优化。

例:

// parallel
long howManyOddNumbersParallel = integerList.parallelStream()
                                            .filter(e -> (e % 2) == 1)
                                            .count();

System.out.println(howManyOddNumbersParallel); // Output: 2

住在 Ideone 上

与容器(或集合 )的差异

虽然可以在 Container 和 Streams 上执行某些操作,但它们最终用于不同的目的并支持不同的操作。容器更关注元素的存储方式以及如何有效地访问这些元素。另一方面,Stream 不能直接访问和操作其元素; 它更专注于作为集体实体的对象组,并作为整体对该实体执行操作。StreamCollection 是针对这些不同目的的单独的高级抽象。