Skip to content

Commit e8e2394

Browse files
committed
🍱 add Streams exercise and homework
1 parent 59fc20e commit e8e2394

File tree

12 files changed

+591
-12
lines changed

12 files changed

+591
-12
lines changed

java8-stream/README.md

Lines changed: 315 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,322 @@
11
# Stream
22

3-
## 中间操作
3+
## 关于流
44

5-
| 操作 | 类型 | 返回类型 | 操作参数 | 函数描述符 |
5+
### 什么是流?
6+
7+
流是Java8引入的全新概念,它用来处理集合中的数据,暂且可以把它理解为一种高级集合。
8+
众所周知,集合操作非常麻烦,若要对集合进行筛选、投影,需要写大量的代码,而流是以声明的形式操作集合,它就像SQL语句,我们只需告诉流需要对集合进行什么操作,它就会自动进行操作,并将执行结果交给你,无需我们自己手写代码。
9+
因此,流的集合操作对我们来说是透明的,我们只需向流下达命令,它就会自动把我们想要的结果给我们。由于操作过程完全由Java处理,因此它可以根据当前硬件环境选择最优的方法处理,我们也无需编写复杂又容易出错的多线程代码了。
10+
11+
### 流的特点
12+
13+
1. 只能遍历一次
14+
我们可以把流想象成一条流水线,流水线的源头是我们的数据源(一个集合),数据源中的元素依次被输送到流水线上,我们可以在流水线上对元素进行各种操作。
15+
一旦元素走到了流水线的另一头,那么这些元素就被“消费掉了”,我们无法再对这个流进行操作。当然,我们可以从数据源那里再获得一个新的流重新遍历一遍。
16+
2. 采用内部迭代方式
17+
若要对集合进行处理,则需我们手写处理代码,这就叫做外部迭代。
18+
而要对流进行处理,我们只需告诉流我们需要什么结果,处理过程由流自行完成,这就称为内部迭代。
19+
20+
### 流的操作种类
21+
22+
流的操作分为两种,分别为中间操作和终端操作。
23+
24+
1. 中间操作
25+
当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“中间操作”。
26+
中间操作仍然会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线。
27+
2. 终端操作
28+
当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终端操作。
29+
终端操作将返回一个执行结果,这就是你想要的数据。
30+
31+
### 流的操作过程
32+
33+
使用流一共需要三步:
34+
35+
1. 准备一个数据源
36+
2. 执行中间操作
37+
中间操作可以有多个,它们可以串连起来形成流水线。
38+
3. 执行终端操作
39+
执行终端操作后本次流结束,你将获得一个执行结果。
40+
41+
## 使用流
42+
43+
### 创建流
44+
45+
在使用流之前,首先需要拥有一个数据源,并通过StreamAPI提供的一些方法获取该数据源的流对象。数据源可以有多种形式:
46+
47+
**1. 集合**
48+
49+
这种数据源较为常用,通过stream()方法即可获取流对象:
50+
51+
```java
52+
List<Person> list = new ArrayList<Person>();
53+
Stream<Person> stream = list.stream();
54+
```
55+
56+
**2. 数组**
57+
58+
通过Arrays类提供的静态函数stream()获取数组的流对象:
59+
60+
```java
61+
String[] names = {"chaimm","peter","john"};
62+
Stream<String> stream = Arrays.stream(names);
63+
```
64+
65+
**3. 值**
66+
67+
直接将几个值变成流对象:
68+
69+
```java
70+
Stream<String> stream = Stream.of("chaimm","peter","john");
71+
```
72+
73+
**4. 文件**
74+
75+
```java
76+
try(Stream lines = Files.lines(Paths.get(“文件路径名”),Charset.defaultCharset())){
77+
//可对lines做一些操作
78+
}catch(IOException e){
79+
}
80+
```
81+
82+
**5. iterator**
83+
84+
**创建无限流**
85+
86+
```java
87+
Stream.iterate(0, n -> n + 2)
88+
.limit(10)
89+
.forEach(System.out::println);
90+
```
91+
92+
> PS:Java7简化了IO操作,把打开IO操作放在try后的括号中即可省略关闭IO的代码。
93+
94+
### 筛选 filter
95+
96+
filter 函数接收一个Lambda表达式作为参数,该表达式返回boolean,在执行过程中,流将元素逐一输送给filter,并筛选出执行结果为true的元素。
97+
如,筛选出所有学生:
98+
99+
```java
100+
List<Person> result = list.stream()
101+
.filter(Person::isStudent)
102+
.collect(toList());
103+
```
104+
105+
### 去重distinct
106+
107+
去掉重复的结果:
108+
109+
```java
110+
List<Person> result = list.stream()
111+
.distinct()
112+
.collect(toList());
113+
```
114+
115+
### 截取
116+
117+
截取流的前N个元素:
118+
119+
```java
120+
List<Person> result = list.stream()
121+
.limit(3)
122+
.collect(toList());
123+
```
124+
125+
### 跳过
126+
127+
跳过流的前n个元素:
128+
129+
```java
130+
List<Person> result = list.stream()
131+
.skip(3)
132+
.collect(toList());
133+
```
134+
135+
### 映射
136+
137+
对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
138+
如,获取每个人的姓名(实则是将Perosn类型转换成String类型):
139+
140+
```java
141+
List<Person> result = list.stream()
142+
.map(Person::getName)
143+
.collect(toList());
144+
```
145+
146+
### 合并多个流
147+
148+
例:列出List中各不相同的单词,List集合如下:
149+
150+
```java
151+
List<String> list = new ArrayList<String>();
152+
list.add("I am a boy");
153+
list.add("I love the girl");
154+
list.add("But the girl loves another girl");
155+
```
156+
157+
思路如下:
158+
159+
首先将list变成流:
160+
161+
```java
162+
list.stream();
163+
```
164+
165+
按空格分词:
166+
167+
```java
168+
list.stream()
169+
.map(line->line.split(" "));
170+
```
171+
172+
分完词之后,每个元素变成了一个String[]数组。
173+
174+
将每个 `String[]` 变成流:
175+
176+
```java
177+
list.stream()
178+
.map(line->line.split(" "))
179+
.map(Arrays::stream)
180+
```
181+
182+
此时一个大流里面包含了一个个小流,我们需要将这些小流合并成一个流。
183+
184+
将小流合并成一个大流:用flagmap替换刚才的map
185+
186+
```java
187+
list.stream()
188+
.map(line->line.split(" "))
189+
.flagmap(Arrays::stream)
190+
```
191+
192+
去重
193+
194+
```java
195+
list.stream()
196+
.map(line->line.split(" "))
197+
.flagmap(Arrays::stream)
198+
.distinct()
199+
.collect(toList());
200+
```
201+
202+
### 是否匹配任一元素:anyMatch
203+
204+
anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
205+
如,判断list中是否有学生:
206+
207+
```java
208+
boolean result = list.stream()
209+
.anyMatch(Person::isStudent);
210+
```
211+
212+
### 是否匹配所有元素:allMatch
213+
214+
allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
215+
如,判断是否所有人都是学生:
216+
217+
```java
218+
boolean result = list.stream()
219+
.allMatch(Person::isStudent);
220+
```
221+
222+
### 是否未匹配所有元素:noneMatch
223+
224+
noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:
225+
226+
```java
227+
boolean result = list.stream()
228+
.noneMatch(Person::isStudent);
229+
```
230+
231+
### 获取任一元素findAny
232+
233+
findAny能够从流中随便选一个元素出来,它返回一个Optional类型的元素。
234+
235+
```java
236+
Optional<Person> person = list.stream().findAny();
237+
```
238+
239+
### 获取第一个元素findFirst
240+
241+
```java
242+
Optional<Person> person = list.stream().findFirst();
243+
```
244+
245+
### 归约
246+
247+
归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。
248+
249+
在流中,reduce函数能实现归约。
250+
reduce函数接收两个参数:
251+
252+
1. 初始值
253+
2. 进行归约操作的Lambda表达式
254+
255+
**元素求和:自定义Lambda表达式实现求和**
256+
257+
例:计算所有人的年龄总和
258+
259+
```java
260+
int age = list.stream().reduce(0, (person1,person2)->person1.getAge()+person2.getAge());
261+
```
262+
263+
1. reduce的第一个参数表示初试值为0;
264+
2. reduce的第二个参数为需要进行的归约操作,它接收一个拥有两个参数的Lambda表达式,reduce会把流中的元素两两输给Lambda表达式,最后将计算出累加之和。
265+
266+
**元素求和:使用Integer.sum函数求和**
267+
268+
上面的方法中我们自己定义了Lambda表达式实现求和运算,如果当前流的元素为数值类型,那么可以使用Integer提供了sum函数代替自定义的Lambda表达式,如:
269+
270+
```java
271+
int age = list.stream().reduce(0, Integer::sum);
272+
```
273+
274+
Integer类还提供了 `min``max` 等一系列数值操作,当流中元素为数值类型时可以直接使用。
275+
276+
### 数值流的使用
277+
278+
采用reduce进行数值操作会涉及到基本数值类型和引用数值类型之间的装箱、拆箱操作,因此效率较低。
279+
当流操作为纯数值操作时,使用数值流能获得较高的效率。
280+
281+
**将普通流转换成数值流**
282+
283+
StreamAPI提供了三种数值流:IntStream、DoubleStream、LongStream,也提供了将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong。
284+
如,将Person中的age转换成数值流:
285+
286+
```java
287+
IntStream stream = list.stream().mapToInt(Person::getAge);
288+
```
289+
290+
**数值计算**
291+
292+
每种数值流都提供了数值计算函数,如max、min、sum等。如,找出最大的年龄:
293+
294+
```java
295+
OptionalInt maxAge = list.stream()
296+
.mapToInt(Person::getAge)
297+
.max();
298+
```
299+
300+
由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理。
301+
此外,mapToInt、mapToDouble、mapToLong进行数值操作后的返回结果分别为:OptionalInt、OptionalDouble、OptionalLong
302+
303+
## 中间操作和收集操作
304+
305+
| 操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
6306
|:-----:|:--------|:-------|:-------|:-------|
7307
| `filter` | 中间 | `Stream<T>` | `Predicate<T>` | `T -> boolean` |
308+
| `distinct` | 中间 | `Stream<T>` | | |
309+
| `skip` | 中间 | `Stream<T>` | long | |
8310
| `map` | 中间 | `Stream<R>` | `Function<T, R>` | `T -> R` |
9-
| `limit` | 中间 | `Stream<T>` | | |
311+
| `flatMap` | 中间 | `Stream<R>` | `Function<T, Stream<R>>` | `T -> Stream<R>` |
312+
| `limit` | 中间 | `Stream<T>` | long | |
10313
| `sorted` | 中间 | `Stream<T>` | `Comparator<T>` | `(T, T) -> int` |
11-
| `distinct` | 中间 | `Stream<T>` | | |
12-
13-
## 收集操作(终端操作)
14-
15-
| 操作 | 类型 | 目的 |
16-
|:-----:|:--------|:-------|
17-
| `forEach` | 收集 | 消费流中的每个元素并对其应用 Lambda。这一操作返回 void |
18-
| `count` | 收集 | 返回流中元素的个数。这一操作返回 long |
19-
| `collect` | 收集 | 把流归约成一个集合,比如 List、Map 甚至是 Integer。 |
314+
| `anyMatch` | 收集 | `boolean` | `Predicate<T>` | `T -> boolean` |
315+
| `noneMatch` | 收集 | `boolean` | `Predicate<T>` | `T -> boolean` |
316+
| `allMatch` | 收集 | `boolean` | `Predicate<T>` | `T -> boolean` |
317+
| `findAny` | 收集 | `Optional<T>` | | |
318+
| `findFirst` | 收集 | `Optional<T>` | | |
319+
| `forEach` | 收集 | `void` | `Consumer<T>` | `T -> void` |
320+
| `collect` | 收集 | `R` | `Collector<T, A, R>` | |
321+
| `reduce` | 收集 | `Optional<T>` | `BinaryOperator<T>` | `(T, T) -> T` |
322+
| `count` | 收集 | `long` | | |

java8-stream/src/main/java/io/github/biezhi/java8/stream/Project.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import lombok.Builder;
44
import lombok.Data;
55

6+
import java.util.ArrayList;
7+
import java.util.List;
8+
69
/**
710
* 项目
811
*
@@ -38,4 +41,30 @@ public class Project {
3841
*/
3942
private String author;
4043

44+
/**
45+
* fork数
46+
*/
47+
private Integer forks;
48+
49+
public static List<Project> buildData(){
50+
List<Project> data = new ArrayList<>();
51+
52+
data.add(Project.builder().name("Blade").language("java").author("biezhi")
53+
.stars(3500).forks(2000).description("Lightning fast and elegant mvc framework for Java8").build());
54+
55+
data.add(Project.builder().name("Tale").language("java").author("biezhi")
56+
.stars(2600).forks(2300).description("Best beautiful java blog, worth a try").build());
57+
58+
data.add(Project.builder().name("Vue.js").language("js").author("yyx990803")
59+
.stars(83000).forks(10322).description("A progressive, incrementally-adoptable JavaScript framework for building UI on the web.").build());
60+
61+
data.add(Project.builder().name("Flask").language("python").author("pallets")
62+
.stars(10500).forks(3000).description("The Python micro framework for building web applications").build());
63+
64+
data.add(Project.builder().name("Elves").language("java").author("biezhi")
65+
.stars(200).forks(100).description("Spider").build());
66+
67+
return data;
68+
}
69+
4170
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.github.biezhi.java8.stream.lesson2;
2+
3+
/**
4+
* 创建流
5+
* <p>
6+
* Stream.of
7+
* Arrays.stream
8+
* collection.stream
9+
* Files.lines
10+
* Stream.iterate
11+
*
12+
* @author biezhi
13+
* @date 2018/2/12
14+
*/
15+
public class Example1 {
16+
17+
public static void main(String[] args) {
18+
19+
}
20+
21+
}

0 commit comments

Comments
 (0)