Java Stream API

时间:2020-01-09 14:19:58  来源:igfitidea点击:

Java Stream API提供了一种处理对象集合的函数方法。 Java Stream API是在Java 8中添加的,同时还具有其他一些函数性编程函数。该Java Stream教程将解释这些函数流的工作方式以及如何使用它们。

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

Java流定义

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

流处理

我们可以将侦听器添加到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);

    }
}

对Stream接口的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实例,该实例表示应用了map操作的原始字符串流。

我们只能将单个操作添加到给定的" 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); });

许多非终端Stream操作可以将Java Lambda表达式作为参数。该lambda表达式实现了适合给定非终端操作的Java函数接口。例如,"函数"或者"Predicate"接口。非终端操作方法参数的参数通常是函数接口,这就是为什么它也可以由Java lambda表达式实现的原因。

filter()

JavaStream``filter()可用于过滤JavaStream中的元素。 "过滤器"方法采用"Predicate",该Predicate针对流中的每个元素进行调用。如果元素要包含在生成的" Stream"中,则" Predicate"应返回" true"。如果不应包含该元素,则"Predicate"应返回" false"。

这是一个调用JavaStream``filter()方法的示例:

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

map()

JavaStream``map()方法将一个元素转换(映射)到另一个对象。例如,如果我们有一个字符串列表,则可以将每个字符串转换为小写,大写或者原始字符串的子字符串,或者完全转换成其他字符串。这是一个JavaStream``map()示例:

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

Stream<String> streamMapped = stream.map((value) -> value.toUpperCase());

flatMap()

JavaStream``flatMap()方法将单个元素映射为多个元素。想法是将每个元素从由多个内部元素组成的复杂结构"展平"到仅由这些内部元素组成的"展平"流。

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

这是一个将字符串的"列表"平面映射到每个字符串中的单词的示例。这个例子应该使我们了解如何使用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))
;

这个JavaStream``flatMap()示例首先创建一个带有3个包含书名的字符串的List。然后获得List的Stream,并调用flatMap()。

在Stream上调用的flatMap()操作必须返回另一个表示Flat映射元素的Stream。在上面的示例中,每个原始字符串都被分解为单词,变成了"列表",然后从该"列表"中获取并返回了流。

请注意,此示例以对终端操作forEach()的调用结束。此调用仅在此处触发内部迭代,从而触发平面映射操作。如果没有在Stream链上调用任何终端操作,则不会发生任何事情。实际上不会进行平面映射。

distinct()

JavaStreamdistinct()方法是一种非终端操作,它返回一个新的Stream,该Stream'将仅包含与原始流不同的元素。任何重复项将被消除。这是JavaStream``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);

在此示例中,元素" one"在原始流中出现2次。只有这个元素的第一次出现会包含在由distinct()返回的Stream中。因此,生成的"列表"(通过调用" collect()")将仅包含"一个","两个"和"三个"。从此示例打印的输出将是:

[one, two, three]

limit()

JavaStreamlimit()方法可以将流中的元素数量限制为作为参数提供给limit()方法的数量。 limit()方法返回一个新的Stream,该Stream最多包含给定数量的元素。这是一个JavaStream``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()

JavaStream``peek()方法是一种非终端操作,它以Consumer(java.util.function.Consumer)作为参数。将为流中的每个元素调用"消费者"。 peek()方法返回一个新的Stream,其中包含原始流中的所有元素。

正如方法所说," 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");
});

终端机操作

Java Stream接口的终端操作通常返回一个值。一旦在" Stream"上调用了终端操作," Stream"和任何链接流的迭代将开始。迭代完成后,将返回终端操作的结果。

终端操作通常不返回新的Stream实例。因此,一旦在流上调用了终端操作,来自非终端操作的" Stream"实例的链接就结束了。这是在JavaStream上调用终端操作的示例:

long count = stream
  .map((value) -> { return value.toLowerCase(); })
  .map((value) -> { return value.toUpperCase(); })
  .map((value) -> { return value.substring(0,3); })
  .count();

在示例末尾,对count()的调用是终端操作。由于count()返回long,因此非终端操作的Stream链(调用map())结束。

anyMatch()

JavaStreamanyMatch()方法是一种终端操作,它以单个Predicate作为参数,启动Stream的内部迭代,并将Predicate参数应用于每个元素。如果Predicate对任何元素返回true,则anyMatch()方法返回true。如果没有元素匹配"Predicate",则" anyMatch()"将返回" false"。这是一个Java Stream`anyMatch()示例:

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();

boolean anyMatch = stream.anyMatch((value) -> { return value.startsWith("One"); });
System.out.println(anyMatch);

在上面的示例中," anyMatch()"方法调用将返回" true",因为流中的第一个字符串元素以" One"开头。

allMatch()

Java的Stream的allMatch()方法是一种终端操作,它以单个Predicate作为参数,启动Stream中元素的内部迭代,并将Predicate参数应用于每个元素。如果Predicate对Stream中的所有元素返回true,则allMatch()将返回true。如果不是所有元素都与Predicate相匹配,则allMatch()方法将返回false。这是一个JavaStream``allMatch()示例:

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();

boolean allMatch = stream.allMatch((value) -> { return value.startsWith("One"); });
System.out.println(allMatch);

在上面的示例中,allMatch()方法将返回false,因为Stream中只有一个字符串以" One"开头。

noneMatch()

JavaStreamnoneMatch()方法是一种终端操作,它将对流中的元素进行迭代并根据流中是否没有与传递给Predicate noneMatch相匹配的元素返回true或者false,作为参数。如果没有任何元素与"Predicate"匹配,则" noneMatch()"方法将返回" true",如果匹配一个或者多个元素,则将返回" false"。这是一个JavaStream``noneMatch()示例:

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

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

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

boolean noneMatch = stream.noneMatch((element) -> {
    return "xyz".equals(element);
});

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

collect()

JavaStream``collect()方法是一种终端操作,它开始元素的内部迭代,并以某种类型的集合或者对象收集流中的元素。这是一个简单的Java Stream collect()方法示例:

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();

List<String> stringsAsUppercaseList = stream
.map(value -> value.toUpperCase())
.collect(Collectors.toList());

System.out.println(stringsAsUppercaseList);

collect()方法采用一个Collector(java.util.stream.Collector)作为参数。实现一个"收集器"需要对"收集器"接口进行一些研究。幸运的是,Java类java.util.stream.Collectors包含了一组预执行的" Collector"实现,我们可以将它们用于最常见的操作。在上面的示例中,使用的是由Collectors.toList()返回的Collector实现。这个"收集器"只是在标准Java"列表"中收集流中的所有元素。

count()

JavaStreamcount()方法是一种终端操作,它开始对Stream中的元素进行内部迭代,并对元素进行计数。这是一个JavaStream``count()示例:

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();

long count = stream.flatMap((value) -> {
    String[] split = value.split(" ");
    return (Stream<String>) Arrays.asList(split).stream();
})
.count();

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

此示例首先创建一个字符串" List",然后为该" List"获取" Stream",为其添加" flatMap()"操作,然后结束对count()的调用。 count()方法将开始Stream中元素的迭代,这将导致在flatMap()操作中将字符串元素拆分为单词,然后进行计数。最终打印出来的结果是14.

findAny()

Java Stream findAny()方法可以从Stream中找到单个元素。找到的元素可以来自" Stream"中的任何位置。无法保证元素从流中的何处获取。这是Java Stream`findAny()的示例:

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

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

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

Optional<String> anyElement = stream.findAny();

System.out.println(anyElement.get());

注意findAny()方法如何返回一个Optional。 " Stream"可能为空,因此无法返回任何元素。我们可以通过OptionalisPresent()方法检查是否找到了元素。

findFirst()

如果Stream中存在任何元素,则Java StreamfindFirst()方法将查找Stream中的第一个元素。 " findFirst()"方法返回一个" Optional",我们可以从中获取元素(如果存在)。这是一个Java Stream`findFirst()示例:

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

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

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

Optional<String> result = stream.findFirst();

System.out.println(result.get());

我们可以通过isPresent()方法检查返回的Optional是否包含元素。

forEach()

JavaStreamforEach()方法是一种终端操作,它启动Stream中元素的内部迭代,并将Consumer(java.util.function.Consumer)应用于该元素中的每个元素流。 forEach()方法返回void。这是一个JavaStream``forEach()示例:

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

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

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

stream.forEach( element -> { System.out.println(element); });

min()

JavaStreammin()方法是一种终端操作,它返回Stream中最小的元素。哪个元素最小是由传递给min()方法的Comparator实现决定的。在我有关对Java集合进行排序的教程中,我已经解释了Comparator接口的工作方式。这是一个JavaStream``min()示例:

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

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

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

Optional<String> min = stream.min((val1, val2) -> {
    return val1.compareTo(val2);
});

String minString = min.get();

System.out.println(minString);

注意min()方法如何返回可能包含或者不包含结果的Optional(可选)。如果Stream是空的,则optional()get()方法将抛出NoSuchElementException。

max()

JavaStreammax()方法是一种终端操作,它返回Stream中最大的元素。哪个元素最大是由传递给max()方法的Comparator实现决定的。我已经在有关排序Java集合的教程中解释了Comparator接口的工作方式。这是一个JavaStreammax()示例:

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

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

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

Optional<String> max = stream.max((val1, val2) -> {
    return val1.compareTo(val2);
});

String maxString = max.get();

System.out.println(maxString);

注意,max()方法如何返回可能包含或者不包含结果的Optional(可选)。如果Stream是空的,则optional()get()方法将抛出NoSuchElementException。

reduce()

JavaStreamreduce()方法是一种终端操作,可以将流中的所有元素缩减为单个元素。这是一个JavaStreamreduce()示例:

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();

Optional<String> reduced = stream.reduce((value, combinedValue) -> {
    return combinedValue + " + " + value;
});

System.out.println(reduced.get());

注意reduce()方法返回的Optional。这个Optional包含了传递给reduce()方法的lambda表达式返回的值(如果有的话)。我们可以通过调用Optional``get()方法获得该值。

toArray()

Java的Stream的toArray()方法是一种终端操作,它启动流中元素的内部迭代,并返回包含所有元素的Object数组。这是一个JavaStreamtoArray()示例:

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();

Object[] objects = stream.toArray();

串流

JavaStream接口包含一个称为concat()的静态方法,该方法可以将两个流连接为一个。结果是一个新的" Stream",其中包含第一个流中的所有元素,然后是第二个流中的所有元素。以下是使用JavaStream``concat()方法的示例:

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> stream1 = stringList.stream();

List<String> stringList2 = new ArrayList<>();
stringList2.add("Lord of the Rings");
stringList2.add("Planet of the Rats");
stringList2.add("Phantom Menace");

Stream<String> stream2 = stringList2.stream();

Stream<String> concatStream = Stream.concat(stream1, stream2);

List<String> stringsAsUppercaseList = concatStream
        .collect(Collectors.toList());

System.out.println(stringsAsUppercaseList);

从数组创建流

Java的Stream接口包含一个称为of()的静态方法,该方法可用于从一个或者多个对象创建Stream。这是使用Javaof()方法的示例:

Stream<String> streamOf = Stream.of("one", "two", "three");