Java Stream API(上篇)

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

Java Stream API提供了处理对象集合的功能方法。Java Stream API与其他几个功能编程功能一起添加到Java 8中。本Java Stream教程将解释这些函数流的工作原理,以及您如何使用它们。

Java Stream API与Java IO的Java InputStream和Java OutputStream无关。InputStreamOutputStream与字节流相关。Java Stream API用于处理对象流,而不是字节。

Java流定义

Java Stream是一个能够对其元素进行内部迭代的组件,这意味着它可以迭代其元素本身。相反,当您使用[Java Collections]迭代功能(例如[Java迭代器]或Java for-each循环与[Java迭代器]一起使用)时,您必须自己实现元素的迭代。

流处理

您可以将侦听器附加到Stream中。Stream在内部迭代元素时,会调用这些侦听器。监听器对流中的每个元素调用一次。这样,每个侦听器都可以处理流中的每个元素。这被称为流处理

小溪的听众形成了一条链子。链中的第一个侦听器可以处理流中的元素,然后为链中的下一个侦听器返回一个新元素进行处理。侦听器可以返回相同的元素或新的元素,具体取决于该侦听器(处理器)的目的。

获取流

获取Java的方法有很多。获取Stream的最常见方法之一是从[Java集合]。以下是从[Java列表中]获取Stream的示例:

List<String> items = new ArrayList<String>();

items.add("one");
items.add("two");
items.add("three");

Stream<String> stream = items.stream();    
复制代码

此示例首先创建一个JavaList,然后向它添加三个[Java字符串]。最后,该示例调用stream()方法以获取Stream实例。

终端和非终端运营

Stream接口有一系列终端 和非终端操作可供选择。非终端流操作是一种无需做任何其他操作即可将侦听器添加到流中的操作。终端流操作是一种启动元素内部迭代、调用所有侦听器并返回结果的操作。

以下是包含非终端操作和终端操作的Java Stream示例:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExamples {

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<String>();

        stringList.add("ONE");
        stringList.add("TWO");
        stringList.add("THREE");

        Stream<String> stream = stringList.stream();

        long count = stream
            .map((value) -> { return value.toLowerCase(); })
            .count();

        System.out.println("count = " + count);

    }
}
复制代码

对流接口的map()方法的调用是一个非终端操作。它只是在流上设置一个lambda表达式,将每个元素转换为小写。稍后将更详细地介绍map()方法。

count()方法的调用是一个终端操作。此调用在内部启动迭代,这将导致每个元素转换为小写,然后计数。

将元素转换为小写实际上不会影响元素计数。转换部分就在那里作为非终端操作的示例。

非终端业务

Java Stream API的非终端流操作是转换或过滤流中元素的操作。当您向流中添加非终端操作时,您将因此获得一个新的流。新流表示应用非终端操作的原始流产生的元素流。以下是添加到流中的非终端操作示例——该操作会导致新流:

List<String> stringList = new ArrayList<String>();

stringList.add("ONE");
stringList.add("TWO");
stringList.add("THREE");
    
Stream<String> stream = stringList.stream();
    
Stream<String> stringStream =
    stream.map((value) -> { return value.toLowerCase(); });
复制代码

注意stream``map()调用。此调用实际上返回一个新的Stream实例,表示应用映射操作的原始字符串流。

您只能向给定的Stream实例添加单个操作。如果您需要相互链式传输多个操作,则需要将第二个操作应用于第一个操作产生的Stream操作。情况如下:

Stream<String> stringStream1 =
        stream.map((value) -> { return value.toLowerCase(); });

Stream<½String> stringStream2 =
        stringStream1.map((value) -> { return value.toUpperCase(); });
复制代码

请注意,第一个map()调用返回的Stream上是如何调用第二个Stream map()调用的。

在Java Stream上将调用链化到非终端操作是很常见的。以下是在Java流上链接非终端操作调用的示例:

Stream<String> stream1 = stream
  .map((value) -> { return value.toLowerCase(); })
  .map((value) -> { return value.toUpperCase(); })
  .map((value) -> { return value.substring(0,3); });
复制代码

许多非终端流操作都可以以[Java Lambda表达式]为参数。此lambda表达式实现了一个适合给定非终端操作的[Java函数接口]。例如,FunctionPredicate接口。非终端操作方法参数的参数通常是一个函数式接口——这就是为什么它也可以通过Java lambda表达式实现。

filter()

Java Stream filter()可用于从Java流中过滤出元素。过滤器方法接受流中的每个元素调用的Predicate。如果元素要包含在生成的流中,那么Predicate应该返回true。如果不应该包含元素,则Predicate应该返回false。

下面是调用Java Stream filter()方法的示例:

Stream<String> longStringsStream = stream.filter((value) -> {
    return value.length() >= 3;
});
复制代码

map()

Java Stream map()方法将元素转换(映射)到另一个对象。例如,如果你有一个字符串列表,它可以将每个字符串转换为小写,大写,或原始字符串的子字符串,或其他完全不同的东西。下面是一个Java Stream ' ' map()示例:

List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream();

Stream<String> streamMapped = stream.map((value) -> value.toUpperCase());
复制代码

flatMap()

Java Stream flatMap()方法将一个元素映射为多个元素。这个想法是,你将每个元素“变平”,从一个由多个内部元素组成的复杂结构,变成一个只由这些内部元素组成的“平坦”流。

例如,假设您有一个嵌套对象(子对象)的对象。然后,您可以将该对象映射到一个由自身及其嵌套对象组成的“平面”流中——或仅由嵌套对象组成。您还可以将元素List流映射到元素本身。或者将字符串流映射到这些字符串中的单词流——或将这些字符串中的单个Character实例映射。

这里有一个示例,将字符串List平面映射到每个字符串中的单词。此示例应让您了解如何使用flatMap()将单个元素映射到多个元素。

List<String> stringList = new ArrayList<String>();

stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");

Stream<String> stream = stringList.stream();

stream.flatMap((value) -> {
    String[] split = value.split(" ");
    return (Stream<String>) Arrays.asList(split).stream();
})
.forEach((value) -> System.out.println(value))
;
复制代码

这个Java Stream flatMap()示例首先创建一个包含图书标题的3个字符串的List。然后获取List的Stream,并调用flatMap()。

Stream上调用的flatMap()操作必须返回另一个表示平面映射元素的Stream。在上面的示例中,每个原始字符串被拆分为单词,转换为List,并从该List获取和返回流。

注意,本示例以调用forEach()结束,这是一个终端操作。这个调用只是为了触发内部迭代,从而触发平面映射操作。如果在流链上没有调用终端操作,则不会发生任何事情。实际上不会发生平面映射。

distinct()

Java Stream distinct()方法是一个非终结操作,它返回一个newStream,该newStream将只包含来自原始流的不同元素。任何重复的都将被删除。下面是Java Stream distinct()方法的一个例子:

List<String> stringList = new ArrayList<String>();

stringList.add("one");
stringList.add("two");
stringList.add("three");
stringList.add("one");

Stream<String> stream = stringList.stream();

List<String> distinctStrings = stream
        .distinct()
        .collect(Collectors.toList());

System.out.println(distinctStrings);
复制代码

在本例中,元素1在原始流中出现了2次。只有这个元素的第一次出现才会包含在distinct()返回的流中。因此,得到的List(调用collect())将只包含一个、两个和三个。从这个示例打印的输出将是:

[one, two, three]
复制代码

limit()

Java Stream limit()方法可以将流中的元素数量限制为limit()方法的参数。limit()方法返回一个最多包含给定数量元素的新Stream。下面是一个Java流' ' limit()的例子:

List<String> stringList = new ArrayList<String>();

stringList.add("one");
stringList.add("two");
stringList.add("three");
stringList.add("one");

Stream<String> stream = stringList.stream();
stream
    .limit(2)
    .forEach( element -> { System.out.println(element); });    
复制代码

此示例首先创建一个Stream,然后在上面调用limit(),然后使用lambda调用forEach(),该lambda打印流中的元素。由于limit(2)调用,将只打印前两个元素。

peek()

Java Stream peek()方法是一个以Consumer(Java .util.function.Consumer)为参数的非终端操作。流中的每个元素都会调用Consumer。peek()方法返回一个包含原始流中所有元素的新流。

正如方法所说,peek()方法的目的是查看流中的元素,而不是转换它们。请记住,peek方法不会启动流中元素的内部迭代。你需要调用终端操作。下面是一个Java Stream peek()示例:

List<String> stringList = new ArrayList<String>();

stringList.add("abc");
stringList.add("def");

Stream<String> stream = stringList.stream();

Stream<String> streamPeeked = stream.peek((value) -> {
    System.out.println("value");
});
复制代码

Guess you like

Origin juejin.im/post/7031348992240222216