|
1 | 1 | # Stream |
2 | 2 |
|
3 | | -## 中间操作 |
| 3 | +## 关于流 |
4 | 4 |
|
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 | +| 操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 | |
6 | 306 | |:-----:|:--------|:-------|:-------|:-------| |
7 | 307 | | `filter` | 中间 | `Stream<T>` | `Predicate<T>` | `T -> boolean` | |
| 308 | +| `distinct` | 中间 | `Stream<T>` | | | |
| 309 | +| `skip` | 中间 | `Stream<T>` | long | | |
8 | 310 | | `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 | | |
10 | 313 | | `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` | | | |
0 commit comments