Java 8 Learn Notes - Streams
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 int, long 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 List, Set 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的更多相关文章
- Java 8 Learn Notes
Main reference: [1] http://winterbe.com/posts/2014/03/16/java-8-tutorial/ [2] https://plus.google.co ...
- 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 ...
- 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 ...
- Linux Academy Learn Notes
Linux Essentials Certification Globbing ls ?.txt --- ? stands for one character while * means one or ...
- [Java coding] leetcode notes
1, 如何不排序而找到最大,次大或者最小值? var int max1, max2, min1; iterate array once: update max1, max2, min1, for ex ...
- Java 8 中的 Streams API 详解
为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...
- Bash Scripting Learn Notes
References: [1] http://www.tldp.org/LDP/Bash-Beginners-Guide/html/ 1. Executing programs from a scri ...
- Java 8中的 Streams API 详解
为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...
- (转)Java 8 中的 Streams API 详解
为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...
随机推荐
- 老李推荐:第4章3节《MonkeyRunner源码剖析》ADB协议及服务: ADB协议概览
老李推荐:第4章3节<MonkeyRunner源码剖析>ADB协议及服务: ADB协议概览 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试, ...
- 云计算之路-阿里云上:RDS数据库连接数过万引发故障,主备库切换后恢复正常
非常抱歉!今天 12:03-12:52 ,由于数据库连接数异常突增超过1万,达到了阿里云RDS的最大连接数限制,影响了全站的正常访问.由此给您带来麻烦,请您谅解. 在发现数据库连接数突增的问题后,我们 ...
- JavaScript 数组操作方法
这些数组的操作方法会改变原来的数组.在使用 Vue 或者 Angular 等框架的时候会非常实用,使用这些方法修改数组会触发视图的更新. Array.prototype.push 该方法可以在数组末尾 ...
- jenkins容器权限被被拒绝
问题,我们从官网上面pull下jenkins后,如果直接运行容器的没问题 docker run -d -p 8080:8080 -v jenkins:latest 不过我们可能需要映射下容器内部的地址 ...
- 通过UDP广播实现Android局域网Peer Discovering
本文是对个人笔记中内容的整理,部分代码及图片来自互联网,由于不好找到原始出处,所以未加注明. 如有痛感,联系删除. 本文将介绍以下知识点: TCP与UDP的区别: 单播.多播.广播: Java中实现U ...
- C++中的继承详解(3)作用域与重定义,赋值兼容规则
作用域与同名隐藏 一样的,先上代码 1 class A 2 { 3 public: 4 int a_data; 5 void a() 6 { 7 cout << "A" ...
- Timer,TimerTask通过程序计数器实现的定时任务
1.程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看 做是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里(仅是概念模型, 各种虚 ...
- Android 开源优秀的项目
webrtc square/picasso Android 的一个强大的图像下载和缓存库 A powerful image downloading and caching library for A ...
- 什么是NoSQL
NoSQL = Not Only SQL 不仅仅是SQL NoSQL,指的是非关系型的数据库(没有声明性查询语言,没有预定义的模式,可以为键 - 值对存储,列存储,文档存储,图形数据库).不同于传统的 ...
- javascript中json对象json数组json字符串互转及取值
今天用到了json数组和json对象和json类型字符串之间互转及取值,记录一下: 1.json类型的字符串转换为json对象及取值 var jsonString = '{"bar" ...