Java 8 Stream

本文介绍了Java8中Stream API的基础知识,包括Stream操作、中间操作与终止操作的区别,以及如何利用filter、map等方法处理数据。此外还探讨了如何通过collect、flatMap和reduce等高级操作来高效地处理复杂的数据流。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java 8 Stream Tutorial

原文链接

java8提供了lambada表达式,函数接口,极大方便开发者。本文是翻译他人文章,语文一般,请见谅。如果有兴趣,可以通过链接阅读原文。

stream

stream代表了一系列元素,并且提供了各种各样的操作。

List<String> myList =
    Arrays.asList("a1", "a2", "b1", "c2", "c1");

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

// C1
// C2

Stream操作要么是中间变量,要么是结束。结束方法返回void或者非Stream result。在上面,filter,map,和 sorted 是中间操作,foreach 是结束操作。
Stream的操作大部分是无干涉或者是无状态的。
比如上面的例子,没有操作修改mylist的元素,也不会依赖外部在执行可能发生改变的值。

各种类型Stream

Stream大部分是通过collections产生。Lists和Sets 支持 stream() 和 parallelStream()。parallelStream表示用多线程来并行处理(处理得当可以增加吞吐量)。本文就只讲stream()。

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

通过调用stream来产生。我们也可以通过下面例子的Stream.of来产生。

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

java8提供了Primitive Stream:IntStream,LongStream,DoubleStream.
通过intStream.range 可以替代for-loop循环

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

// 1
// 2
// 3

primitive stream支持sum() 和 average()操作。

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

我们也可以通过mapToInt ,mapToLong,mapToDouble 把objectStream 转化 primitive Stream

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

Primitive Stream 也可以通过 mapToObj 转化 object Stream

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

// a1
// a2
// a3

这里是把一个double stream 先转化成 int stream 再转化成 obj stream

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

// a1
// a2
// a3

执行顺序

非终止性操作的一个重要特征是 laziness(懒).看下面的例子

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和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都是五次调用,而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只调用一次,所以操作管道执行更快更多的元素。记得以后把filter放在前头。
让我们扩展上面的例子中,一个额外的操作,sorted:

Stream.of("d2", "a2", "b1", "b3", "c")
    .sorted((s1, s2) -> {
        System.out.printf("sort: %s; %s\n", 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

首先,所有collection都参与了排序。换句话说水平执行排序。
再次我们可以通过重新编排链的执行顺序来优化性能:

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\n", 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

java8 Streams 不能重复使用。只要你调用任何终端操作流即关闭:

Stream<String> 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将导致以下Exception:

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)

对于每一个终端操作,我们必须创建一个新的steam,为了克服这种局限性,我们可以创建一个流supplier,来构建所有中间操作:

Supplier<Stream<String>> 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()就构造了一个新的Stream,该stream保存所需的终端操作。

高级操作

stream支持大量不同的操作。我们已经了解filter或map最重要的操作。查看所有其他可用操作(请参见stream Javadoc)。让我们更深入地研究更复杂的操作collect,flatMap,reduce。

从本节中的大多数代码引用下面的Person例子:

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name;
    }
}

List<Person> persons =
    Arrays.asList(
        new Person("Max", 18),
        new Person("Peter", 23),
        new Person("Pamela", 23),
        new Person("David", 12));

Collect

Collect是流的元素转变成一种不同的结果,比如List,Set和Map。Collect接受四个不同的操作的Collector:supplier,accumulator,combiner和finisher。这听起来起初超级复杂,但Java 8已经提供大部分操作了,支持各种内置的collectors 通过Collectors类。因此,对于最常见的操作,你不必自己实现一个Collector。

让我们先从常见的用例:

List<Person> filtered =
    persons
        .stream()
        .filter(p -> p.name.startsWith("P"))
        .collect(Collectors.toList());

System.out.println(filtered);    // [Peter, Pamela]

它是非常简单的构造从流到list。需要set时 - 只使用Collectors.toSet()

下一个例子所有人员按年龄分组

ap<Integer, List<Person>> personsByAge = persons
    .stream()
    .collect(Collectors.groupingBy(p -> p.age));

personsByAge
    .forEach((age, p) -> System.out.format("age %s: %s\n", age, p));

// age 18: [Max]
// age 23: [Peter, Pamela]
// age 12: [David]

collectors都非常灵活。你还可以在流中的元素创建聚合,例如所有人员的平均年龄

Double averageAge = persons
    .stream()
    .collect(Collectors.averagingInt(p -> p.age));

System.out.println(averageAge);     // 19.0

如果您有兴趣更全面的统计,总结性collectors返回一个特殊的内置的汇总统计的对象。因此,我们可以简单地确定最小值,最大值和人员的算术平均年龄以及总和和计数。

IntSummaryStatistics ageSummary =
    persons
        .stream()
        .collect(Collectors.summarizingInt(p -> p.age));

System.out.println(ageSummary);
// IntSummaryStatistics{count=4, sum=76, min=12, average=19.000000, max=23}

接下来示例将所有人员连成一个字符串:

String phrase = persons
    .stream()
    .filter(p -> p.age >= 18)
    .map(p -> p.name)
    .collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));

System.out.println(phrase);
// In Germany Max and Peter and Pamela are of legal age.

连接的collector 接受一个分隔符,以及一个可选的前缀和后缀。

为了将stream转成map,我们必须同时指定键和值应该如何被映射。请记住,映射的键必须是唯一的,否则抛出IllegalStateException。可以选择合并函数作为附加参数来绕过异常:

Map<Integer, String> map = persons
    .stream()
    .collect(Collectors.toMap(
        p -> p.age,
        p -> p.name,
        (name1, name2) -> name1 + ";" + name2));

System.out.println(map);
// {18=Max, 23=Peter;Pamela, 12=David}

现在我们知道了一些最强大的内置collectors,让我们尝试建立特殊collector。我们要把persons of stream 转化成包括所有名字的大写,用|隔开。为了实现这一点,我们通过创建一个Collector,用Collector.of()。我们必须通过Collector的四种元素:supplier
,accumulator,combiner和finisher。

Collector<Person, StringJoiner, String> personNameCollector =
    Collector.of(
        () -> new StringJoiner(" | "),          // supplier
        (j, p) -> j.add(p.name.toUpperCase()),  // accumulator
        (j1, j2) -> j1.merge(j2),               // combiner
        StringJoiner::toString);                // finisher

String names = persons
    .stream()
    .collect(personNameCollector);

System.out.println(names);  // MAX | PETER | PAMELA | DAVID

由于Java中的字符串是不可改变的,我们需要一个辅助类像StringJoiner让collector建构我们的字符串。supplier通过分隔符构造StringJoiner。accumulator用于添加每个人的名字大写到StringJoiner。combiner知道如何把两个StringJoiners合并成一个。在最后步骤中,finisher从StringJoiner构造所需字符串。

FlatMap

我们已经学会了如何利用map操作流的对象转换为另一种类型的对象。map是有点限制,因为每个对象只能映射到一个其他对象。但是,如果我们想要一个对象转换成多个其他对象或者根本没有?这flatMap就派上用场了。

FlatMap变换流中的每个元素到其他对象流。所以每个对象将被改造成零,一个或多个由流支持的其他对象。这些流中的内容将被放置到flatMap的返回流。

我们需要一个合适的类型:

class Foo {
    String name;
    List<Bar> bars = new ArrayList<>();

    Foo(String name) {
        this.name = name;
    }
}

class Bar {
    String name;

    Bar(String name) {
        this.name = name;
    }
}

接下来,我们利用流实例化一个连接对象:

List<Foo> foos = new ArrayList<>();

// create foos
IntStream
    .range(1, 4)
    .forEach(i -> foos.add(new Foo("Foo" + i)));

// create bars
foos.forEach(f ->
    IntStream
        .range(1, 4)
        .forEach(i -> f.bars.add(new Bar("Bar" + i + " <- " + f.name))));

现在我们有三个foos,每组有3个bars。

FlatMap接受其返回对象流的方法。因此,为了解决每个foo的bar,我们只是通过相应的功能:

foos.stream()
    .flatMap(f -> f.bars.stream())
    .forEach(b -> System.out.println(b.name));

// Bar1 <- Foo1
// Bar2 <- Foo1
// Bar3 <- Foo1
// Bar1 <- Foo2
// Bar2 <- Foo2
// Bar3 <- Foo2
// Bar1 <- Foo3
// Bar2 <- Foo3
// Bar3 <- Foo3

正如你所看到的,我们已经成功地将3个foo的对象流分为9个对象流。

最后,上面的代码示例可以简化为流操作的单一管道:

IntStream.range(1, 4)
    .mapToObj(i -> new Foo("Foo" + i))
    .peek(f -> IntStream.range(1, 4)
        .mapToObj(i -> new Bar("Bar" + i + " <- " f.name))
        .forEach(f.bars::add))
    .flatMap(f -> f.bars.stream())
    .forEach(b -> System.out.println(b.name));

FlatMap也可用于在Java8中引入的optional 类。Optionals flatMap操作返回另一种类型的option object。因此,它可以避免烦人的null检查。

class Outer {
    Nested nested;
}

class Nested {
    Inner inner;
}

class Inner {
    String foo;
}

你必须添加多个null检查,以防止可能发生的NullPointerException异常,outer
实例的内部Inner String:

Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}

相同的行为可以通过利用Option 的 flatMap操作:

Optional.of(new Outer())
    .flatMap(o -> Optional.ofNullable(o.nested))
    .flatMap(n -> Optional.ofNullable(n.inner))
    .flatMap(i -> Optional.ofNullable(i.foo))
    .ifPresent(System.out::println);

每个对flatMap的调用将会返回想要的object如果不为空的话。

Reduce

reduce将流的所有元素组合成一个单一的结果。java 8支持三种不同的reduce方法。第一个是从stream中得到一个元素。让我们看看如何使用这种方法来确定年纪最大的人:

persons
    .stream()
    .reduce((p1, p2) -> p1.age > p2.age ? p1 : p2)
    .ifPresent(System.out::println);    // Pamela

reduce接受binaryoperator函数。这实际上是一个双功能,操作同一个类型。bifunction接受两个参数。示例函数将两个人的年龄进行比较,以返回最大年龄的人。
第二个reduce接受特定值和binaryoperator蓄电池。这种方法可以用来构建一个新的人,该人的名称和年龄是其他人的名称和年龄的集合:

Person result =
    persons
        .stream()
        .reduce(new Person("", 0), (p1, p2) -> {
            p1.age += p2.age;
            p1.name += p2.name;
            return p1;
        });

System.out.format("name=%s; age=%s", result.name, result.age);
// name=MaxPeterPamelaDavid; age=76

第三个reduce接受三个参数:一个标识值,accumulator和cominer,类型是binaryoperator。由于特定值并不局限于人的类型,我们可以利用这个减少,以确定所有的年龄的总和:

Integer ageSum = persons
    .stream()
    .reduce(0, (sum, p) -> sum += p.age, (sum1, sum2) -> sum1 + sum2);

System.out.println(ageSum);  // 76

正如你可以看到的结果是76,但到底发生了什么,让我们通过一些调试输出扩展上面的代码:

Integer ageSum = persons
    .stream()
    .reduce(0,
        (sum, p) -> {
            System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
            return sum += p.age;
        },
        (sum1, sum2) -> {
            System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
            return sum1 + sum2;
        });

// accumulator: sum=0; person=Max
// accumulator: sum=18; person=Peter
// accumulator: sum=41; person=Pamela
// accumulator: sum=64; person=David

正如你可以看到的accumulator。它第一次被调用的初始身份值0和名字Max。在接下来的三个步骤和不断增加的最后一个步骤的人的年龄达到76岁。

然而combiner为什么没被调用?并行执行相同的流将揭开秘密:

Integer ageSum = persons
    .parallelStream()
    .reduce(0,
        (sum, p) -> {
            System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
            return sum += p.age;
        },
        (sum1, sum2) -> {
            System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
            return sum1 + sum2;
        });

// accumulator: sum=0; person=Pamela
// accumulator: sum=0; person=David
// accumulator: sum=0; person=Max
// accumulator: sum=0; person=Peter
// combiner: sum1=18; sum2=23
// combiner: sum1=23; sum2=12
// combiner: sum1=41; sum2=35

并行执行是完全不同的执行行为。现在combiner被执行了。由于accumulator被并行执行,combiner需要把单独的累加值再累加。
让我们深入到平行流在下一章。

Parallel Streams

流可以并行执行,以增加大量的输入元素的运行时性能。并行stream流,使用一个共同的forkjoinpool,通过静态ForkJoinPool.commonPool() 方法。底层的线程池的大小使用最多五个线程-取决于可用的物理内核的数量:

ForkJoinPool commonPool = ForkJoinPool.commonPool();
System.out.println(commonPool.getParallelism());    // 3

在我的机器上,该值是3.可以通过JVM参数进行设置

-Djava.util.concurrent.ForkJoinPool.common.parallelism=5

collection支持方法parallelstream()创建一个元素并行stream。或者你可以通过方法parallel()转换为并行Stream。
为了描述并行执行的具体行为,下面的例子将会把执行的线程打印出来:

Arrays.asList("a1", "a2", "b1", "c2", "c1")
    .parallelStream()
    .filter(s -> {
        System.out.format("filter: %s [%s]\n",
            s, Thread.currentThread().getName());
        return true;
    })
    .map(s -> {
        System.out.format("map: %s [%s]\n",
            s, Thread.currentThread().getName());
        return s.toUpperCase();
    })
    .forEach(s -> System.out.format("forEach: %s [%s]\n",
        s, Thread.currentThread().getName()));

通过输出信息,能更好的了解执行过程

filter:  b1 [main]
filter:  a2 [ForkJoinPool.commonPool-worker-1]
map:     a2 [ForkJoinPool.commonPool-worker-1]
filter:  c2 [ForkJoinPool.commonPool-worker-3]
map:     c2 [ForkJoinPool.commonPool-worker-3]
filter:  c1 [ForkJoinPool.commonPool-worker-2]
map:     c1 [ForkJoinPool.commonPool-worker-2]
forEach: C2 [ForkJoinPool.commonPool-worker-3]
forEach: A2 [ForkJoinPool.commonPool-worker-1]
map:     b1 [main]
forEach: B1 [main]
filter:  a1 [ForkJoinPool.commonPool-worker-3]
map:     a1 [ForkJoinPool.commonPool-worker-3]
forEach: A1 [ForkJoinPool.commonPool-worker-3]
forEach: C1 [ForkJoinPool.commonPool-worker-2]

正如你可以看到的平行流利用所有可用线程从常见的forkjoinpool执行流操作。输出可能在连续运行时不同,因为实际使用的线程的行为是不确定的。
让我们通过一个额外的流操作来扩展示例,Srot:

Arrays.asList("a1", "a2", "b1", "c2", "c1")
    .parallelStream()
    .filter(s -> {
        System.out.format("filter: %s [%s]\n",
            s, Thread.currentThread().getName());
        return true;
    })
    .map(s -> {
        System.out.format("map: %s [%s]\n",
            s, Thread.currentThread().getName());
        return s.toUpperCase();
    })
    .sorted((s1, s2) -> {
        System.out.format("sort: %s <> %s [%s]\n",
            s1, s2, Thread.currentThread().getName());
        return s1.compareTo(s2);
    })
    .forEach(s -> System.out.format("forEach: %s [%s]\n",
        s, Thread.currentThread().getName()));

结果看起来会比较奇怪陌生

filter:  c2 [ForkJoinPool.commonPool-worker-3]
filter:  c1 [ForkJoinPool.commonPool-worker-2]
map:     c1 [ForkJoinPool.commonPool-worker-2]
filter:  a2 [ForkJoinPool.commonPool-worker-1]
map:     a2 [ForkJoinPool.commonPool-worker-1]
filter:  b1 [main]
map:     b1 [main]
filter:  a1 [ForkJoinPool.commonPool-worker-2]
map:     a1 [ForkJoinPool.commonPool-worker-2]
map:     c2 [ForkJoinPool.commonPool-worker-3]
sort:    A2 <> A1 [main]
sort:    B1 <> A2 [main]
sort:    C2 <> B1 [main]
sort:    C1 <> C2 [main]
sort:    C1 <> B1 [main]
sort:    C1 <> C2 [main]
forEach: A1 [ForkJoinPool.commonPool-worker-1]
forEach: C2 [ForkJoinPool.commonPool-worker-3]
forEach: B1 [main]
forEach: A2 [ForkJoinPool.commonPool-worker-2]
forEach: C1 [ForkJoinPool.commonPool-worker-1]

似乎Srot是只在主线程上被执行的。事实上,在一个并行流Sort使用新的java 8方法parallelsort()。正如在Javadoc,这种方法是由数组的长度决定着将顺序或并行执行:

If the length of the specified array is less than the minimum granularity, then it is sorted using the appropriate Arrays.sort method

回到最后一节的reduce的例子。我们已经发现combiner只在并行中才被调用。让我们看看哪些线程实际上涉及:

List<Person> persons = Arrays.asList(
    new Person("Max", 18),
    new Person("Peter", 23),
    new Person("Pamela", 23),
    new Person("David", 12));

persons
    .parallelStream()
    .reduce(0,
        (sum, p) -> {
            System.out.format("accumulator: sum=%s; person=%s [%s]\n",
                sum, p, Thread.currentThread().getName());
            return sum += p.age;
        },
        (sum1, sum2) -> {
            System.out.format("combiner: sum1=%s; sum2=%s [%s]\n",
                sum1, sum2, Thread.currentThread().getName());
            return sum1 + sum2;
        });

结果说明了combiner和accumulator 在并行流中都被执行。

accumulator: sum=0; person=Pamela; [main]
accumulator: sum=0; person=Max;    [ForkJoinPool.commonPool-worker-3]
accumulator: sum=0; person=David;  [ForkJoinPool.commonPool-worker-2]
accumulator: sum=0; person=Peter;  [ForkJoinPool.commonPool-worker-1]
combiner:    sum1=18; sum2=23;     [ForkJoinPool.commonPool-worker-1]
combiner:    sum1=23; sum2=12;     [ForkJoinPool.commonPool-worker-2]
combiner:    sum1=41; sum2=35;     [ForkJoinPool.commonPool-worker-2]

总之,它可以说,并行流可以带来一个很好的性能提升到流与大量的输入元素。但要记住,一些并行流操作,如reduce和collect需要的额外的计算(combine操作),在顺序执行中却是不需要的。
此外,我们已经了解到,所有的并行操作流共享相同的JVM并且拥有共同forkjoinpool。因此,您可能希望避免实施缓慢的阻塞流操作,因为这可能会减慢应用程序的其他严重依赖并行Stream的部分。

总结

我的java编程指南Sttreams在这里结束。如果你在学习更多关于java 8流感兴趣,我给你推荐的javadoc文档流包。如果你想更多的了解背后的机制,你可能想读Martin Fowlers的 article about Collection Pipelines。
如果你有兴趣在JavaScript中一样,你可能想在stream.js - JavaScript实现的java 8流API一看。你可能还想阅读我的java教程8和我的java教程8犀牛。
希望本教程是有帮助的,你和你喜欢阅读它。本教程中的全部源代码托管在GitHub。随时frok库或通过推特发送给我你的反馈。

Happy coding!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值