下面来看一下借助`Java 8`的`StreamAPl`,什么才叫优雅:
```java
public class StreamIteratorDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张三丰");
list.add("周芷若");
list.add("赵敏");
list.add("张三");
list.stream()
.filter(name->name.startsWith("张"))
.filter(name->name.length() == 3)
.forEach(name-> System.out.println(name));
}
}
```
## 流式思想概述
注意:请暂时忘记对传统`IO`流的固有印象!
整体来看,流式思想类似于工厂车间的“生产流水线”
当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能
及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去
执行它。

这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。
这里的filter、map、skip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。
> 备注:`Stream流`其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
`Stream`(流)是一个来自数据源的元素队列
- 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
- 数据源流的来源。可以是集合,数组等。
和以前的`Collection`操作不同,`Stream`操作还有两个基础的特征:
- `Pipelining`:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格(`fluent style`)。这样做可以对操作进行优化,比如延迟执行(`laziness`)和短路(`short-circuiting`)。
- 内部迭代:以前对集合遍历都是通过`Iterator`或者增强`for`的方式,显式的在集合外部进行迭代,这叫做外部迭代。`Stream`提供了内部迭代的方式,流可以直接调用遍历方法。
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(`source`)→数据转换一执行操作获取想要的结果,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
## 获取流
`java.uti1.stream.stream<T>`是`Java8`新加入的最常用的流接口。(这并不是一个函数式接口)获取一个流非常简单,有以下几种常用的方式:
- 所有的`Collection `集合都可以通过`stream `默认方法获取流;
- `stream`接口的静态方法`of`可以获取数组对应的流。
### 根据Collection获取流
首先,`java.util.collection` 接口中加入了`default`方法`stream`用来获取流,所以其所有实现类均可获取流。
```java
public class GetStreamDemo {
public static void main(String[] args) {
// 把集合转为Stream流,通过stream方法获取
List<String> list = new ArrayList<>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream();
}
}
```
### 根据Map获取流
```java
// 间接将map转为stream
Map<String, String> map = new HashMap<>();
Set<String> mapKeySet = map.keySet();
Stream<String> keySetStream = mapKeySet.stream();
Collection<String> mapValues = map.values();
Stream<String> collectionStream = mapValues.stream();
Set<Map.Entry<String, String>> mapEntrySet = map.entrySet();
Stream<Map.Entry<String, String>> entrySetStream = mapEntrySet.stream();
```
### 根据数组获取`Stream`流
```java
int[] helloInt = {1, 2, 3, 4, 5};
Stream<int[]> helloIntStream = Stream.of(helloInt);
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
String[] helloString = {"a", "bb", "cc"};
Stream<String> helloStringStream = Stream.of(helloString);
```
## 常用方法

流模型的操作很丰富,这里介绍一些常用的`APl`。这些方法可以被分成两种:
- 延迟方法:返回值类型仍然是`Stream `接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
- 终结方法:返回值类型不再是`stream `接口自身类型的方法,因此不再支持类似`stringBuilder`那样的链式调用。本小节中,终结方法包括 `count`和`forEach `方法。
> 备注:本小节之外的更多方法,请自行参考API文档。
### 逐一处理:forEach
```java
public class StreamForEachDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("二麻子");
Stream<String> stream = list.stream();
// 使用forEach对流进行遍历
stream.forEach(name -> System.out.println(name));
}
}
```
### 过滤:filter
可以通过`filter `方法将一个流转换成另一个子集流。方法签名:
```java
Stream<T> filter(Predicate<? super T> predicate);
```
该接口接收个`Predicate`函数式接口参数(可以是一个`Lambda`或方
法引用)作为筛选条件。

此前已经学习过`java.util.stream.Predicate`函数式接口,其中的方法为:
```java
boolean test(T t)
```
该方法将会产生一个`boolean`值结果,代表指定的条件是否满足。如
果结果为`true`,那么`Stream`流的`filter `方法将会留用元素;如果
结果为`false`,那么`filter`方法将会舍弃元素。
Stream流中的filter方法基本使用的代码如:
```java
public class StreamFilterDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("张三丰");
list.add("张二狗");
list.add("赵六");
list.add("二麻子");
// 执行filter后会返回Stream流,可以继续对Stream流进行操作,链式调用
Stream<String> listStream = list.stream()
.filter(name->name.startsWith("张"))
.filter(name->name.length() == 3);
listStream.forEach(name -> System.out.println(name));
}
}
```
`Stream`属于管道流,只能被消费一次,第一个`Stream`流被调用完毕,数据就会流转到下一个`Stream`上,而这时第一个`Stream`流已经使用完毕就关闭了,所以第一个`Stream`流不能再调用方法。
```java
// 对上述代码消费两次
listStream.forEach(name -> System.out.println(name));
listStream.forEach(name -> System.out.println(name));
```
执行结果为:
```java
张三丰
张二狗
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
```
### 映射:map
如果需要将流中的元素映射到另一个流中,可以使用map方法。方法签名:
```java
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
```
该接口需要一个`Function`函数式接口参数,可以将当前流中的`T`类型数据转换为另一种`R`类型的流。

此前我们已经学习过`java.util.stream.Function`函数式接口,其中唯一的抽象方法为:
```java
R apply(T t);
```
这可以将一种`T`类型转换成为`R`类型,而这种转换的动作,就称为“映射”。
`Stream`流中的`map`方法基本使用的代码如:
```java
public class StreamMapDemo {
public static void main(String[] args) {
// 获取Stream
Stream<String> stream = Stream.of("5", "12", "34");
//将字符串Stream转为Integer类型的Stream
Stream<Integer> integerStream = stream.map(str -> Integer.parseInt(str));
integerStream.forEach(num -> System.out.println(num));
}
}
```
### 统计个数:count
正如旧集合`Collection `当中的`size`方法一样,流提供`count`方法来数一数其中的元素个数:
```java
long count();
```
该方法返回一个`long`值代表元素个数(不再像旧集合那样是`int`值)。基本使用:
```java
public class StreamCountDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A man");
list.add("needs to");
list.add("know");
list.add("when to stand up");
list.add("for himself");
long count = list.stream().count();
System.out.println(count);
}
}
```
结果为:
```java
5
```
### 截取:limit
`limit `方法可以对流进行截取,只取用前`n`个。方法签名:
```java
Stream<T> limit(long maxsize);
```
参数是一个`long`型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:

```java
public class StreamLimitDemo {
public static void main(String[] args) {
// 获取一个Stream流
String[] strings = {"万界神主","斗罗大陆","斗破苍穹"};
Stream<String> stringStream = Stream.of(strings);
// 使用limit方法对Stream流中的元素截取只要前2个
stringStream.limit(2).forEach(name-> System.out.println(name));
}
}
```
运行结果:
```java
万界神主
斗罗大陆
```
### 跳过:skip
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:
```java
Stream<T> skip(long n);
```
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:

```java
/**
* Stream流中的Skip方法:用于跳过元素
* 如果希望跳过前几个元素,可以使用skip方法获取一个截取后的新流
* 如果流的当前长度大于n,则跳过前n个,否则会得到一个长度为0的空流
* @author guqin
* @date 2019-07-23 20:53
*/
public class StreamSkipDemo {
public static void main(String[] args) {
String[] strings = {"万界神主","斗罗大陆","斗破苍穹","天行九歌"};
Stream<String> stringStream = Stream.of(strings);
stringStream.skip(2)
.forEach(name-> System.out.println(name));
}
}
```
运行结果:
```java
斗破苍穹
天行九歌
```
### 组合:concat
如果有两个流,希望合并成为一个流,那么可以使用 stream 接口的静态方法`concat`:
```java
static <T> Stream<T> concat(Stream<? extends T) a, Stream<? extends T) b);
```
> 备注:这是一个静态方法,与`java.lang.String`当中的`concat `方法是不同的。
该方法的基本使用代码如:
```java
public class StreamConcatDemo {
public static void main(String[] args) {
Stream<String> stringStream1 = Stream.of("万界神主","斗罗大陆");
Stream<String> stringStream2 = Stream.of("斗破苍穹","天行九歌");
Stream.concat(stringStream1, stringStream2)
.forEach(name -> System.out.println(name));
}
}
```
运行结果:
```
万界神主
斗罗大陆
斗破苍穹
天行九歌
```

Java 8新特性之Stream流