资讯专栏INFORMATION COLUMN

Java 8 Strem基本操作

Jensen / 2139人阅读

摘要:可以使用方法替换常规循环以上代码的产出所有这些原始流都像常规对象流一样工作,但有以下不同之处原始流使用专门的表达式,例如代替或代替。原始流支持额外的终端聚合操作,以上代码的产出有时将常规对象流转换为基本流是有用的,反之亦然。

本文提供了有关Java 8 Stream的深入概述。当我第一次读到的Stream API,我感到很困惑,因为它听起来类似Java I/O的InputStreamOutputStream。但Java 8 Stream是完全不同的东西。Streams是Monads,因此在为Java提供函数式编程方面发挥了重要作用:

在函数式编程中,monad是表示定义为步骤序列的计算的结构。具有monad结构的类型定义链操作的含义,或将该类型的函数嵌套在一起。

本文详解如何使用Java 8 Stream以及如何使用不同类型的可用流操作。您将了解处理顺序以及流操作的顺序如何影响运行时性能。并对更强大的reducecollectflatMap流操作详细介绍。

如果您还不熟悉Java 8 lambda表达式,函数接口和方法引用,那么您可能需要了解Java 8。

Stram如何工作

Stream表示一系列元素,并支持不同类型的操作以对这些元素执行计算:

List streams =
    Arrays.asList("a1", "a2", "b1", "c2", "c1");

streams
    .stream()
    .filter(s -> s.startsWith("c"))
    .map(String::toUpperCase)
    .sorted()
    .forEach(System.out::println);

以上代码的产出:

C1
C2

Stream操作是中间操作或终端操作。中间操作返回一个流,因此我们可以链接多个中间操作而不使用分号。终端操作无效或返回非流结果。在上述例子中filtermapsorted是中间操作,而forEach是一个终端的操作。有关所有可用流操作的完整列表,请参阅Stream Javadoc。如上例中所见的这种流操作链也称为操作管道。

大多数流操作都接受某种lambda表达式参数,这是一个指定操作的确切行为的功能接口。大多数这些操作必须是不受干扰和无状态。

当函数不修改流的基础数据源时,该函数是不受干扰的,例如在上面的示例中,没有lambda表达式通过从集合中添加或删除元素来修改streams。

当操作的执行是确定性的时,函数是无状态的,例如在上面的示例中,没有lambda表达式依赖于任何可变变量或来自外部作用域的状态,其可能在执行期间改变。

不同种类的Stream

可以从各种数据源创建流,尤其是集合。ListsSets支持新的方法stream()parallelStream()来创建顺序流或并行流。并行流能够在多个线程上操作,后面的部分将对此进行介绍。我们现在关注的是顺序流:

Arrays.asList("a1", "a2", "a3")
    .stream()
    .findFirst()
    .ifPresent(System.out::println);

以上代码的产出:

a1

在对象列表上调用stream()方法将返回常规对象流。但是我们不必创建集合以便使用流,就像我们在下一个代码示例中看到的那样:

Stream.of("a1", "a2", "a3")
    .findFirst()
    .ifPresent(System.out::println);

以上代码的产出:

a1

只是用来Stream.of()从一堆对象引用创建一个流。

除了常规对象流之外,Java 8还附带了特殊类型的流,用于处理原始数据类型intlong以及double。你可能已经猜到了IntStreamLongStream,DoubleStream

IntStreams可以使用IntStream.range()方法替换常规for循环:

IntStream.range(1, 4)
    .forEach(System.out::println);

以上代码的产出:

1
2
3

所有这些原始流都像常规对象流一样工作,但有以下不同之处:原始流使用专门的lambda表达式,例如IntFunction代替FunctionIntPredicate代替Predicate。原始流支持额外的终端聚合操作,sum(),average()

Arrays.stream(new int[] {1, 2, 3})
    .map(n -> 2 * n + 1)
    .average()
    .ifPresent(System.out::println);

以上代码的产出:

5.0

有时将常规对象流转换为基本流是有用的,反之亦然。为此,对象流支持特殊的映射操作mapToInt()mapToLong()mapToDouble

Stream.of("a1", "a2", "a3")
    .map(s -> s.substring(1))
    .mapToInt(Integer::parseInt)
    .max()
    .ifPresent(System.out::println);

以上代码的产出:

3

可以通过mapToObj()方式将原始流转换为对象流:

IntStream.range(1, 4)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

以上代码的产出:

a1
a2
a3

下面是一个组合示例:双精度流首先映射到int流,然后映射到字符串的对象流:

Stream.of(1.0, 2.0, 3.0)
    .mapToInt(Double::intValue)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

以上代码的产出:

a1
a2
a3
处理过程

现在我们已经学会了如何创建和使用不同类型的流,让我们深入了解如何在流程下处理流操作。

中间操作的一个重要特征是懒惰。查看缺少终端操作的示例:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    });

执行此代码段时,不会向控制台打印任何内容。这是因为只有在存在终端操作时才执行中间操作。

让我们通过forEach终端操作扩展上面的例子:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    })
    .forEach(s -> System.out.println("forEach: " + s));

执行此代码段会在控制台上产生所需的输出:

filter:  d2
forEach: d2
filter:  a2
forEach: a2
filter:  b1
forEach: b1
filter:  b3
forEach: b3
filter:  c
forEach: c

结果的顺序可能会令人惊讶。默认认为是在流的所有元素上一个接一个地水平执行操作。但相反,每个元素都沿着链垂直移动。第一个字符串“d2”通过filter,然后forEach,然后处理第二个字符串“a2”。

此行为可以减少对每个元素执行的实际操作数,如下一个示例所示:

Stream.of("d2", "a2", "b1", "b3", "c")
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .anyMatch(s -> {
        System.out.println("anyMatch: " + s);
        return s.startsWith("A");
    });

代码产出

map:      d2
anyMatch: D2
map:      a2
anyMatch: A2

一旦谓词应用于给定的输入元素,anyMatch操作将返回true。这对于传递给“A2”的第二个元素是正确的。由于流链的垂直执行,map在这种情况下映射只需执行两次。因此,不是映射流的所有元素,而是map尽可能少地调用。

复杂的处理过程

下一个示例包括两个map,filter中间操作和forEach终端操作。让我们再次检查这些操作是如何执行的:

Stream.of("d2", "a2", "b1", "b3", "c")
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("A");
    })
    .forEach(s -> System.out.println("forEach: " + s));

代码产出:

map:     d2
filter:  D2
map:     a2
filter:  A2
forEach: A2
map:     b1
filter:  B1
map:     b3
filter:  B3
map:     c
filter:  C

正如您可能已经猜到的,对于底层集合中的每个字符串,map和filter都被调用5次,而forEach只被调用一次。

如果我们改变操作的顺序,移动filter到链的开头,我们可以大大减少实际的执行次数:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("a");
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("forEach: " + s));

代码产出:

filter:  d2
filter:  a2
map:     a2
forEach: A2
filter:  b1
filter:  b3
filter:  c

现在,map只调用一次,因此操作管道对大量输入元素的执行速度要快得多。在编写复杂的方法链时要记住这一点。

让我们通过一个sorted额外的操作来扩展上面的例子:

Stream.of("d2", "a2", "b1", "b3", "c")
    .sorted((s1, s2) -> {
        System.out.printf("sort: %s; %s
", s1, s2);
        return s1.compareTo(s2);
    })
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("a");
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("forEach: " + s));

排序是一种特殊的中间操作。这是一个所谓的有状态操作,因为为了对在排序期间必须维护状态的元素集合进行排序。

执行此示例将导致以下控制台输出:

sort:    a2; d2
sort:    b1; a2
sort:    b1; d2
sort:    b1; a2
sort:    b3; b1
sort:    b3; d2
sort:    c; b3
sort:    c; d2
filter:  a2
map:     a2
forEach: A2
filter:  b1
filter:  b3
filter:  c
filter:  d2

首先,对整个输入集合执行排序操作。换句话说,sorted是水平执行的。因此,在这种情况下sorted,对输入集合中的每个元素的多个组合调用八次。

我们可以通过重新排序链来优化性能:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("a");
    })
    .sorted((s1, s2) -> {
        System.out.printf("sort: %s; %s
", s1, s2);
        return s1.compareTo(s2);
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("forEach: " + s));

代码产出

filter:  d2
filter:  a2
filter:  b1
filter:  b3
filter:  c
map:     a2
forEach: A2

在此示例sorted从未被调用过,因为filter将输入集合减少到只有一个元素。因此,对于较大的输入集合,性能会大大提高。

重用Stream

Java 8 Stream无法重用。只要您调用任何终端操作,流就会关闭:

Stream stream =
    Stream.of("d2", "a2", "b1", "b3", "c")
        .filter(s -> s.startsWith("a"));

stream.anyMatch(s -> true);    // ok
stream.noneMatch(s -> true);   // exception

在同一流上的anyMatch之后调用noneMatch会导致以下异常:

java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
    at com.winterbe.java8.Streams5.test7(Streams5.java:38)
    at com.winterbe.java8.Streams5.main(Streams5.java:28)

为了克服这个限制,我们必须为我们想要执行的每个终端操作创建一个新的流链,例如我们可以创建一个流供应商来构建一个新的流,其中已经设置了所有中间操作:

Supplier> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
            .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok

每次调用get()构造一个我们保存的新流,以调用所需的终端操作。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72892.html

相关文章

  • Java 8 Strem高级操作

    摘要:接受包含四种不同操作的操作供应商,累加器,组合器和修整器。累加器用于将每个人的大写名称添加到。第二种方法接受标识值和累加器。由于累加器是并行调用的,因此需要组合器来对各个累加值求和。 Streams支持大量不同的操作。我们已经了解了最重要的操作,如filter,map。发现所有其他可用的操作(参见Stream Javadoc)。我们深入研究更复杂的操作collect,flatMap,r...

    dadong 评论0 收藏0
  • Java8-10-Stream分组与分区详解

    摘要:上一篇我们介绍了的概念与实际的一些操作,本篇我们继续来学习的另一个重要操作,分组与分区。注意到分组后的返回类型是,结果集中会将作为,对应的集合作为返回。 上一篇我们介绍了Strem的概念与实际的一些操作,本篇我们继续来学习Stream的另一个重要操作,分组与分区。我们在上一篇介绍Stream的操作时,会经常使用到Collectors这个类,这个类实际上是一个封装了很多常用的汇聚操作的一...

    shengguo 评论0 收藏0
  • cnn卷积神经网络打造人脸登录系统

    摘要:本文基于环境,采用为基础来构建实时人脸检测与识别系统,探索人脸识别系统在现实应用中的难点。对于人脸检测方法,效果好于的方法,但是检测力度也难以达到现场应用标准。本文中,我们采用了基于深度学习方法的人脸检测系统。 git地址:https://github.com/chenlinzho... 本文主要介绍了系统涉及的人脸检测与识别的详细方法,该系统基于python2.7.10/opencv...

    jackwang 评论0 收藏0
  • cnn卷积神经网络打造人脸登录系统

    摘要:本文基于环境,采用为基础来构建实时人脸检测与识别系统,探索人脸识别系统在现实应用中的难点。对于人脸检测方法,效果好于的方法,但是检测力度也难以达到现场应用标准。本文中,我们采用了基于深度学习方法的人脸检测系统。 git地址:https://github.com/chenlinzho... 本文主要介绍了系统涉及的人脸检测与识别的详细方法,该系统基于python2.7.10/opencv...

    KavenFan 评论0 收藏0
  • Python3爬虫下载pdf(一)

    摘要:爬虫下载一最近在学习的爬虫,并且玩的不亦说乎,因此写个博客,记录并分享一下。 Python3爬虫下载pdf(一) 最近在学习python的爬虫,并且玩的不亦说乎,因此写个博客,记录并分享一下。 需下载以下模块 bs4 模块 requests 模块 一、源码 功能:下载指定url内的所有的pdf 语法:将含有pdf的url放到脚本后面执行就可以了 from bs4 import...

    instein 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<