Files
www/blogs/java/java8/java8-2.md
zhuhjay 22e48d9558 build(www): 添加 Drone CI 流水线配置
- 新增 .drone.yml 文件用于定义 CI/CD 流程
- 配置了基于 Docker 的部署步骤
- 设置了工作区和卷映射以支持持久化数据
- 添加了构建准备阶段和 Docker 部署阶段
- 定义了环境变量和代理设置
- 配置了 artifacts 目录的处理逻辑
- 添加了 timezone 映射以确保时间同步
- 设置了 docker.sock 映射以支持 Docker in Docker
2025-11-01 13:36:00 +08:00

810 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: JDK8 特性(二)
date: 2022-05-24
sidebar: 'auto'
tags:
- JDK8
categories:
- Java
---
## 1 Stream流
### 1.1 集合处理数据的弊端
- 案例引入:
1. 首先筛选所有姓张的人;
2. 然后筛选名字有三个字的人;
3. 最后进行对结果进行打印输出。
- 传统实现
```java
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三丰", "周芷若", "张无忌", "赵敏", "张强");
// 1. 首先筛选所有姓张的人;
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if(name.contains("张")){
zhangList.add(name);
}
}
// 2. 然后筛选名字有三个字的人;
List<String> threeList = new ArrayList<>();
for (String name : zhangList) {
if(name.length() == 3){
threeList.add(name);
}
}
// 3. 最后进行对结果进行打印输出。
for (String name : threeList) {
System.out.println(name);
}
}
}
```
- Stream流式实现
```java
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三丰", "周芷若", "张无忌", "赵敏", "张强");
// 1. 首先筛选所有姓张的人; 2. 然后筛选名字有三个字的人; 3. 最后进行对结果进行打印输出
list.stream()
.filter(str -> str.contains("张"))
.filter(str -> str.length() == 3)
.forEach(System.out::println);
}
}
```
### 1.2 Stream流式思想概述
**注意Stream和IO流(InputStream/OutputStream)没有任何关系请暂时忘记对传统IO流的固有印象**
Stream流式思想类似于工厂车间的“生产流水线”Stream流不是一种数据结构不保存数据而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上通过多个工序让一个原材料加工成一个商品。
![](/java8/Stream流式思想.png)
![](/java8/Stream流式思想1.png)
Stream API能让我们快速完成许多复杂的操作如筛选、切片、映射、查找、去除重复统计匹配和归约。
### 1.3 获取Stream流的两种方式
- 方式一
- Collection集合接口中有默认方法`default Stream<E> stream(){...}`
- 也就是说实现该接口的集合类都可以直接调用stream()方法获取流对象
- 方式二
- Stream中的静态方法`static<T> Stream<T> of(T t){...}`
- 可以调用该方法获取流对象
### 1.4 了解Stream流的常用方法和注意事项
- Stream常用方法
| 方法名 | 方法作用 | 返回值类型 | 方法种类 |
| ------- | ---------- | ---------- | -------- |
| count | 统计个数 | long | 终结 |
| forEach | 逐一处理 | void | 终结 |
| filter | 过滤 | Stream | 函数拼接 |
| limit | 取用前几个 | Stream | 函数拼接 |
| skip | 跳过前几个 | Stream | 函数拼接 |
| map | 映射 | Stream | 函数拼接 |
| concat | 组合 | Stream | 函数拼接 |
- 终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。
- 非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法。)
- Stream注意事项
1. Stream只能操作一次
2. Stream方法返回的是新的流
3. Stream不调用终结方法中间的操作不会执行
#### forEach
- forEach用来遍历流中的数据 -> 终结方法
```text
void forEach(Consumer<? super T> action);
```
- 该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void forEach(){
list.stream().forEach(System.out::println);
}
}
```
#### count
- count方法来统计其中的元素个数 -> 终结方法
```text
long count();
```
- 该方法返回一个long值代表元素个数。基本使用
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void count(){
System.out.println(list.stream().count());
}
}
```
#### filter
- filter用于过滤数据返回符合过滤条件的数据 -> 非终结方法
```text
Stream<T> filter(Predicate<? super T> predicate);
```
- ![](/java8/filter.png)
- 该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void filter(){
// 计算名字长度为3的人数
System.out.println(list.stream().filter(name -> name.length() == 3).count());
}
}
```
#### limit
- limit 方法可以对流进行截取只取用前n个 -> 非终结方法
```text
Stream<T> limit(long maxSize);
```
- 参数是一个long型如果集合当前长度大于参数则进行截取。否则不进行操作
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void limit(){
list.stream().limit(3).forEach(System.out::println);
}
}
```
#### skip
- 如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流 -> 非终结方法
```text
Stream<T> skip(long n);
```
- 如果流的当前长度大于n则跳过前n个; 否则将会得到一个长度为0的空流。
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void skip(){
list.stream().skip(3).forEach(System.out::println);
}
}
```
#### map
- 如果需要将流中的元素映射到另一个流中,可以使用 map 方法 -> 非终结方法
```text
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
```
- 该接口需要一个 Function 函数式接口参数可以将当前流中的T类型数据转换为另一种R类型的流
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void map(){
list.stream()
// 类型增强
.map(name -> "name: " + name)
.forEach(System.out::println);
}
}
```
#### sorted
- 如果需要将数据排序,可以使用 sorted 方法。 -> 非终结方法
```text
Stream<T> sorted(); // 根据元素的自然顺序排序
Stream<T> sorted(Comparator<? super T> comparator); // 自定义比较器排序
```
- 该方法可以有两种不同地排序实现
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void sorted(){
list.stream()
// 默认自然序
.sorted()
// 执行比较器进行排序
.sorted(((o1, o2) -> o1.length() - o2.length()))
.forEach(System.out::println);
}
}
```
#### distinct
- 如果需要去除重复数据,可以使用 distinct 方法 -> 非终结方法
- 自定义类型是根据对象的 hashCode 和 equals 来去除重复元素的
```text
Stream<T> distinct();
```
- 基本使用
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void distinct(){
list.add("迪丽热巴");
list.add("张无忌");
list.stream()
.distinct()
.forEach(System.out::println);
}
}
```
#### match
- 如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法 -> 终结方法
```text
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
```
- 基本使用
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void match(){
// allMatch 所有元素都需要满足
System.out.println(list.stream()
.allMatch(name -> name.contains("张")));
// anyMatch 如果有元素满足即可
System.out.println(list.stream()
.anyMatch(name -> name.contains("张")));
// noneMatch 所有元素都不满足
System.out.println(list.stream()
.noneMatch(name -> name.contains("男")));
}
}
```
#### find
- 如果需要找到某些数据,可以使用 find 相关方法 -> 终结方法
```text
Optional<T> findFirst();
Optional<T> findAny();
```
- 基本使用
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void find(){
// 都是获取第一个元素
Optional<String> optional = list.stream().findAny();
Optional<String> optional1 = list.stream().findFirst();
System.out.println(optional.get());
System.out.println(optional1.get());
}
}
```
#### max && min
- 如果需要获取最大和最小值,可以使用 max 和 min 方法 -> 终结方法
```text
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
```
- 基本使用
```java
public class StreamTest {
@Test
public void min_max(){
// 传入一个比较器, 排序完后获取最后一个
Optional<Integer> max = Stream.of(4, 2, 7, 1).max((o1, o2) -> o1 - o2);
System.out.println(max.get());
// 传入一个比较器, 排序完后获取第一个
Optional<Integer> min = Stream.of(4, 2, 7, 1).min((o1, o2) -> o1 - o2);
System.out.println(min.get());
}
}
```
#### reduce
- 如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法 -> 终结方法
```text
T reduce(T identity, BinaryOperator<T> accumulator);
```
- 基本使用
```java
public class StreamTest {
@Test
public void reduce(){
Integer reduce = Stream.of(4, 5, 3, 9)
// T identity: 默认值
// BinaryOperator<T> accumulator: 对数据的处理方式
// 获取最大值, 初始值为0
.reduce(0, Integer::max);
System.out.println(reduce);
reduce = Stream.of(4, 5, 3, 9)
// T identity: 默认值
// BinaryOperator<T> accumulator: 对数据的处理方式
// 获取加和结果, 初始值为0
.reduce(0, Integer::sum);
System.out.println(reduce);
}
}
```
![](/java8/reduce执行流程.png)
#### map && reduce结合
- 使用案例
```java
public class StreamTest {
@Test
public void reduceAndMap(){
// 获取最大年龄
Optional<Integer> maxAge = Stream.of(
new Person("张三", 18),
new Person("李四", 20),
new Person("王五", 17),
new Person("小红", 19),
new Person("小明", 18))
.map(Person::getAge)
.reduce(Integer::max);
System.out.println(maxAge.get());
// 获取年龄总和
Optional<Integer> totalAge = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 17),
new Person("小红", 19),
new Person("小明", 21))
.map(Person::getAge)
.reduce(Integer::sum);
System.out.println(totalAge.get());
}
}
```
#### mapToInt
- 如果需要将Stream中的Integer类型数据转成int类型可以使用 mapToInt 方法 -> 终结方法
```text
IntStream mapToInt(ToIntFunction<? super T> mapper);
```
- ![](/java8/filter.png)
- 该接口需要一个 Function 函数式接口参数可以将当前流中的T类型数据转换为另一种R类型的流
```java
public class StreamTest {
@Test
public void mapToInt(){
// Integer占用的内存比int多, 在Stream流中会自动拆箱装箱
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
// 将Integer类型转换为int类型, 可以节省空间
IntStream intStream = stream
.mapToInt(Integer::intValue);
}
}
```
#### concat
- 如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat -> 终结方法
- 这是一个静态方法,与 `java.lang.String` 当中的 concat 方法是不同的
```text
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b){...}
```
- 基本使用
```java
public class StreamTest {
@Test
public void concat(){
Stream<String> streamA = Stream.of("张三");
Stream<String> streamB = Stream.of("李四");
// 将以上流合并, 合并的流就不能够继续操作了
Stream.concat(streamA, streamB).forEach(System.out::println);
}
}
```
### 1.5 Stream综合案例
- 现在有两个 ArrayList 集合存储队伍当中的多个成员姓名要求使用Stream实现若干操作步骤
1. 第一个队伍只要名字为3个字的成员姓名
2. 第一个队伍筛选之后只要前3个人
3. 第二个队伍只要姓张的成员姓名;
4. 第二个队伍筛选之后不要前2个人
5. 将两个队伍合并为一个队伍;
6. 根据姓名创建 Person 对象;
7. 打印整个队伍的Person对象信息。
```java
public class StreamCaseDemo {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
List<String> two = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
Collections.addAll(two, "古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
Stream<String> streamOne = one.stream()
// 1. 第一个队伍只要名字为3个字的成员姓名
.filter(name -> name.length() == 3)
// 2. 第一个队伍筛选之后只要前3个人
.limit(3);
Stream<String> streamTwo = two.stream()
// 3. 第二个队伍只要姓张的成员姓名
.filter(name -> name.startsWith("张"))
// 4. 第二个队伍筛选之后不要前2个人
.skip(2);
// 5. 将两个队伍合并为一个队伍
Stream<String> stream = Stream.concat(streamOne, streamTwo);
// 6. 根据姓名创建 Person 对象
stream.map(Person::new)
// 7. 打印整个队伍的Person对象信息
.forEach(System.out::println);
}
}
```
### 1.6 收集Stream流中的结果
- 对流操作完成之后,如果需要将流的结果保存到数组或集合中,可以收集流中的数据
#### 收集结果到集合中
- Stream流提供 collect 方法,其参数需要一个`java.util.stream.Collector<T,A, R>`接口对象来指定收集到哪种集合中。`java.util.stream.Collectors`类提供一些方法可以作为Collector`接口的实例:
- `public static <T> Collector<T, ?, List<T>> toList()`: 转换为 List 集合
- `public static <T> Collector<T, ?, Set<T>> toSet()`: 转换为 Set 集合
- `public static <T, C extends Collection<T>>
Collector<T, ?, C> toCollection(Supplier<C> collectionFactory)`: 收集到指定的集合中
#### 收集结果到数组中
- Stream提供 toArray 方法来将结果放到一个数组中
```text
Object[] toArray(); // 返回Object数组
<A> A[] toArray(IntFunction<A[]> generator); // 返回指定数组
```
#### 对流中数据进行聚合计算
- 当我们使用Stream流处理数据后可以像数据库的聚合函数一样对某个字段进行操作。比如获取最大值获取最小值求总和平均值统计数量。
```java
public class StreamCollectTest {
@Test
public void aggregation(){
List<Person> personList = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 17),
new Person("小红", 19),
new Person("小明", 21))
.collect(Collectors.toList());
// 获取最大值
Optional<Person> optional = personList.stream()
.collect(Collectors.maxBy((o1, o2) -> o1.getAge() - o2.getAge()));
System.out.println("最大值: " + optional.get());
// 获取最小值
Optional<Person> optionalMin = personList.stream()
.collect(Collectors.minBy((o1, o2) -> o1.getAge() - o2.getAge()));
System.out.println("最小值: " + optionalMin.get());
// 求总和
Integer totalAge = personList.stream()
.collect(Collectors.summingInt(Person::getAge));
System.out.println("总和: " + totalAge);
// 平均值
Double avg = personList.stream()
.collect(Collectors.averagingInt(Person::getAge));
System.out.println("平均值: " + avg);
// 统计数量
Long count = personList.stream()
.collect(Collectors.counting());
System.out.println("总共: " + count);
// 以上方法聚合
IntSummaryStatistics summaryStatistics = personList.stream()
.collect(Collectors.summarizingInt(Person::getAge));
System.out.println("上面所有方法的聚合: " + summaryStatistics);
}
}
```
#### 对流中数据进行分组
- 当我们使用Stream流处理数据后可以根据某个属性将数据分组
```java
public class StreamCollectTest {
@Test
public void group(){
List<Person> personList = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 18),
new Person("小红", 18),
new Person("小明", 21))
.collect(Collectors.toList());
// 通过年龄来进行分组
personList.stream()
.collect(Collectors.groupingBy(Person::getAge))
// 分组结果为键值对
.forEach((key, value) -> System.out.println(key + "::" + value));
// 将年龄大于19的分为一组, 小于19分为一组
personList.stream()
.collect(Collectors.groupingBy(s -> {
if(s.getAge() > 19){
return ">=19";
}else{
return "<19";
}
}))
// 分组结果为键值对, 键为groupingBy返回的数据
.forEach((k, v) -> System.out.println(k + "::" + v));
}
}
```
#### 对流中数据进行多级分组
- 还可以对数据进行多级分组:
```java
public class StreamCollectTest {
@Test
public void multiGroup(){
List<Person> personList = Stream.of(
new Person("张三丰", 18),
new Person("迪丽热巴", 22),
new Person("古力娜扎", 18),
new Person("迪迦奥特曼", 18),
new Person("宇宙无敌法外狂徒张三", 21))
.collect(Collectors.toList());
// 先根据年龄分组, 每组中再根据名字的长度分组
personList.stream()
.collect(
// 根据年龄分组
Collectors.groupingBy(p -> {
if(p.getAge() > 19){
return "大于19";
}else{
return "小于等于19";
}
},
// 根据名字长度分组
Collectors.groupingBy(p -> {
if(p.getName().length() > 4){
return "较长的名字";
}else{
return "较短的名字";
}
}))
)
// 结果的类型为: Map<String, Map<String, Person>>
.forEach((oneK, oneV) -> {
System.out.println("年龄" + oneK);
oneV.forEach((twoK, twoV) -> {
System.out.println("\t" + twoK + "::" + twoV);
});
});
/*
result -> {
年龄小于等于19
较长的名字::[Person{name='迪迦奥特曼', age=18}]
较短的名字::[Person{name='张三丰', age=18}, Person{name='古力娜扎', age=18}]
年龄大于19
较长的名字::[Person{name='宇宙无敌法外狂徒张三', age=21}]
较短的名字::[Person{name='迪丽热巴', age=22}]
}
*/
}
}
```
#### 对流中数据进行分区
- `Collectors.partitioningBy`会根据值是否为true把集合分割为两个列表一个true列表一个false列表
- ![](/java8/流数据分区.png)
```java
public class StreamCollectTest {
@Test
public void partition(){
List<Person> personList = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 18),
new Person("小红", 18),
new Person("小明", 21))
.collect(Collectors.toList());
personList.stream()
// 将结果分为, true和false两个分区
.collect(Collectors.partitioningBy(p -> p.getAge() > 19))
.forEach((k, v) -> System.out.println(k + "::" + v));
}
}
```
#### 对流中数据进行拼接
- `Collectors.joining`会根据指定的连接符,将所有元素连接成一个字符串
```java
public class StreamCollectTest {
@Test
public void join(){
List<Person> personList = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 18),
new Person("小红", 18),
new Person("小明", 21))
.collect(Collectors.toList());
// 根据一个字符拼接
String names = personList.stream()
.map(Person::getName)
.collect(Collectors.joining("-"));
System.out.println(names);
// 根据三个字符拼接
names = personList.stream()
.map(Person::getName)
// 参数说明: 分隔符, 前缀, 后缀
.collect(Collectors.joining(",", "{", "}"));
System.out.println(names);
}
}
```