Main reference

[1] http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples

1. How Streams Work

A stream represents a sequence of elements and supports different kind of operations to perform computations upon those elements:

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

2. Different Kinds of Streams

Streams can be created from various data sources, especially collections. Lists and Sets support new methods stream() and parallelStream() to either create a sequential or a parallel stream.

For Sequencial Stream

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

Calling the method stream() on a list of objects returns a regular object stream. But we don't have to create collections in order to work with streams as we see in the next code sample:

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

Just use Stream.of() to create a stream from a bunch of object references.

Besides regular object streams Java 8 ships with special kinds of streams for working with the primitive data types intlong and double. As you might have guessed it's IntStream,LongStream and DoubleStream.

IntStreams can replace the regular for-loop utilizing IntStream.range()

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

All those primitive streams work just like regular object streams with the following differences: Primitive streams use specialized lambda expressions, e.g. IntFunction instead ofFunction or IntPredicate instead of Predicate. And primitive streams support the additional terminal aggregate operations sum() and average()

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

Sometimes it's useful to transform a regular object stream to a primitive stream or vice versa. For that purpose object streams support the special mapping operations mapToInt(),mapToLong() and mapToDouble

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

Primitive streams can be transformed to object streams via mapToObj()

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

Here's a combined example: the stream of doubles is first mapped to an int stream and than mapped to an object stream of strings:

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

3. Processing Order

An important characteristic of intermediate operations is laziness.

4. Reusing Streams

Java 8 streams cannot be reused. As soon as you call any terminal operation the stream is closed

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

To overcome this limitation we have to to create a new stream chain for every terminal operation we want to execute, e.g. we could create a stream supplier to construct a new stream with all intermediate operations already set up:

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

Each call to get() constructs a new stream on which we are save to call the desired terminal operation.

5. Advanced Operations

Most code samples from this section use the following list of persons for demonstration purposes:

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 is an extremely useful terminal operation to transform the elements of the stream into a different kind of result, e.g. a ListSet or Map. Collect accepts a Collector which consists of four different operations: a supplier, an accumulator, a combiner and a finisher. This sounds super complicated at first, but the good part is Java 8 supports various built-in collectors via the Collectors class. So for the most common operations you don't have to implement a collector yourself.

List<Person> filtered =
persons
.stream()
.filter(p -> p.name.startsWith("P"))
.collect(Collectors.toList()); System.out.println(filtered); // [Peter, Pamela]

Then

Map<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 are extremely versatile. You can also create aggregations on the elements of the stream, e.g. determining the average age of all persons:

Double averageAge = persons
.stream()
.collect(Collectors.averagingInt(p -> p.age)); System.out.println(averageAge); // 19.0

In order to transform the stream elements into a map, we have to specify how both the keys and the values should be mapped. Keep in mind that the mapped keys must be unique, otherwise an IllegalStateException is thrown. You can optionally pass a merge function as an additional parameter to bypass the exception:

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}

Now that we know some of the most powerful built-in collectors, let's try to build our own special collector. We want to transform all persons of the stream into a single string consisting of all names in upper letters separated by the | pipe character. In order to achieve this we create a new collector via Collector.of(). We have to pass the four ingredients of a collector: a supplier, an accumulator, a combiner and a 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

Since strings in Java are immutable, we need a helper class like StringJoiner to let the collector construct our string. The supplier initially constructs such a StringJoiner with the appropriate delimiter. The accumulator is used to add each persons upper-cased name to the StringJoiner. The combiner knows how to merge two StringJoiners into one. In the last step the finisher constructs the desired String from the StringJoiner.

FlatMap

We've already learned how to transform the objects of a stream into another type of objects by utilizing the map operation. Map is kinda limited because every object can only be mapped to exactly one other object. But what if we want to transform one object into multiple others or none at all? This is where flatMap comes to the rescue.

FlatMap transforms each element of the stream into a stream of other objects. So each object will be transformed into zero, one or multiple other objects backed by streams. The contents of those streams will then be placed into the returned stream of the flatMap operation.

Before we see flatMap in action we need an appropriate type hierarchy:

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;
}
}

Next, we utilize our knowledge about streams to instantiate a couple of objects:

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

Now we have a list of three foos each consisting of three bars.

FlatMap accepts a function which has to return a stream of objects. So in order to resolve the bar objects of each foo, we just pass the appropriate function:

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

Finally, the above code example can be simplified into a single pipeline of stream operations:

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 is also available for the Optional class introduced in Java 8. Optionals flatMapoperation returns an optional object of another type. So it can be utilized to prevent nastynull checks.

Think of a highly hierarchical structure like this:

class Outer {
Nested nested;
} class Nested {
Inner inner;
} class Inner {
String foo;
}

In order to resolve the inner string foo of an outer instance you have to add multiple null checks to prevent possible NullPointerExceptions:

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

The same behavior can be obtained by utilizing optionals flatMap operation:

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

Reduce

The reduction operation combines all elements of the stream into a single result. Java 8 supports three different kind of reduce methods. The first one reduces a stream of elements to exactly one element of the stream.

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

The second reduce method accepts both an identity value and a BinaryOperatoraccumulator. This method can be utilized to construct a new Person with the aggregated names and ages from all other persons in the stream:

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

The third reduce method accepts three parameters: an identity value, a BiFunctionaccumulator and a combiner function of type BinaryOperator. Since the identity values type is not restricted to the Person type, we can utilize this reduction to determine the sum of ages from all persons:

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

6. Parallel Streams

It can be stated that parallel streams can bring be a nice performance boost to streams with a large amount of input elements. But keep in mind that some parallel stream operations like reduce and collect need additional computations (combine operations) which isn't needed when executed sequentially.

Furthermore we've learned that all parallel stream operations share the same JVM-wide common ForkJoinPool. So you probably want to avoid implementing slow blocking stream operations since that could potentially slow down other parts of your application which rely heavily on parallel streams.

Java 8 Learn Notes - Streams的更多相关文章

  1. Java 8 Learn Notes

    Main reference: [1] http://winterbe.com/posts/2014/03/16/java-8-tutorial/ [2] https://plus.google.co ...

  2. Forget Java to learn Javascript from 0.--Day 1

    The first day,when I read 'we need practice so we need a Javascript Interpreter.','Every browser has ...

  3. Forget Java to learn Javascript from 0.--Preface

    I'm going to start to learn Javascript in this month. Someone told me you can't learn another langua ...

  4. Linux Academy Learn Notes

    Linux Essentials Certification Globbing ls ?.txt --- ? stands for one character while * means one or ...

  5. [Java coding] leetcode notes

    1, 如何不排序而找到最大,次大或者最小值? var int max1, max2, min1; iterate array once: update max1, max2, min1, for ex ...

  6. Java 8 中的 Streams API 详解

    为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...

  7. Bash Scripting Learn Notes

    References: [1] http://www.tldp.org/LDP/Bash-Beginners-Guide/html/ 1. Executing programs from a scri ...

  8. Java 8中的 Streams API 详解

    为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...

  9. (转)Java 8 中的 Streams API 详解

    为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...

随机推荐

  1. POPTEST培训:web自动化测试之DOM

    POPTEST培训:web自动化测试之DOM   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq ...

  2. nodejs oj在线笔试应对方案(讲几种输入处理方法)

    最近参加了一些线上笔试.但是...我不是学计算机的,只会js不会c++,java,c(好吧都学过,不过忘了).可怕的是我也没学过nodejs,怎么 办,怎么办.node不就是用的js吗?所以只用学会标 ...

  3. 非负矩阵分解(1):准则函数及KL散度

    作者:桂. 时间:2017-04-06  12:29:26 链接:http://www.cnblogs.com/xingshansi/p/6672908.html 声明:欢迎被转载,不过记得注明出处哦 ...

  4. Neuron:Neural activities in V1 create a bottom-up saliency map

    Neural activities in V1 create a bottom-up saliency map 本文证明了人类的初级视皮层可以在视觉信息加工的非常早期阶段,生成视觉显著图,用以引导空间 ...

  5. 解决IE6下 PNG图片有背景问题

    IE6下有时候png格式的图片会存在背景的问题,以下是我常用的解决办法: <!--[if IE 6]> <script src="js/DD_belatedPNG_0.0. ...

  6. 做一个完整的纯react-naitve安卓应用【从环境安装到应用发布】

    前提:从来没有写过android 跟 ios 应用,是一个小前端.前一段时间玩了一下 react-native 感觉还不错,应用代码还未开源. 环境: win7 成果:                 ...

  7. Twitter数据抓取的方法(三)

    Scraping Tweets Directly from Twitters Search – Update Published August 1, 2015 Sorry for my delayed ...

  8. Linux - 进程间通信 - 命名管道

    1.命名管道的特点: (1)是管道,可用于非血缘关系的进程间的通信 (2)使用命名管道时,梁金成需要用路径表示通道. (3)命名管道以FIFO的文件形式存储于文件系统中.(FIFO:总是按照先进先出的 ...

  9. final修饰的变量是引用不能变还是对象的内容不能变?

    int a=1;此时a是变量: StringBuffer a=new StringBuffer();此时a就是引用变量,可以说是a引用String对象,通过a来操作String 对象 final St ...

  10. PHP 学习笔记(4)

    声明类属性或方法为静态,就可以不实例化类而直接访问.静态属性不能通过一个类已实例化的对象来访问(但静态方法可以). PHP 5 支持抽象类和抽象方法.定义为抽象的类不能被实例化 使用接口(interf ...