• 函数式编程–stream 流

概述

为什么学

img

img

函数式编程思想

img

Lambda 表达式

概述

img

使用情况

  • 参数为一个接口,并且只有一个抽象方法 (不包含抽象方法|)
  • 例如:

img

例 1

img

例 2

img

例 4

img

例 5

img

✨ 省略规则

img

img

img

img

Stream 流

https://www.bilibili.com/video/BV1Gh41187uR?p=12&spm_id_from=pageDriver&vd_source=30924cc6debe913490fb34d7e3b62fdd

概述

img

初始化工程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class StreamDemo {
public static void main(String[] args) {
List<Author> authors = getAuthors();
System.out.println(authors);
}

private static List<Author> getAuthors() {
// 数据机始化
Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟哲理的祖安人", null);
Author author2 = new Author(2L, "亚拉索", 33, "狂风也追逐不上他的思考速度", null);
Author author3 = new Author(3L, "易", 14, "是这个世界在限制他的思维", null);
Author author4 = new Author(3L, "易", 14, "是这个世界在限制他的思维", null);

List<Book> books1 = new ArrayList<Book>();
List<Book> books2 = new ArrayList<Book>();
List<Book> books3 = new ArrayList<Book>();

books1.add(new Book(1L, "西游记", "冒险,奇幻", 90, "西天取经"));
books2.add(new Book(2L, "三国演义", "社会", 99, "三国演义"));
books3.add(new Book(3L, "红楼梦", "abc", 70, "ggg"));

author.setBooks(books1);
author2.setBooks(books2);
author3.setBooks(books3);
author4.setBooks(books3);

return new ArrayList<>(Arrays.asList(author, author2, author3, author4));
}
}

// [Author(id=1, name=蒙多, age=33, intro=一个从菜刀中明悟哲理的祖安人,
// books=[Book(id=1, name=西游记, category=冒险,奇幻, score=90, intro=西天取经)]),
// Author(id=2, name=亚拉索, age=33, intro=狂风也追逐不上他的思考速度, books=[Book(id=2,
// name=三国演义, category=社会, score=99, intro=三国演义)]), Author(id=3, name=易,
// age=14, intro=是这个世界在限制他的思维, books=[Book(id=3, name=红楼梦, category=abc,
// score=70, intro=ggg)]), Author(id=3, name=易, age=14, intro=是这个世界在限制他的思维,
// books=[Book(id=3, name=红楼梦, category=abc, score=70, intro=ggg)])]

快速入门

  • 获取年龄小于 18 的作者 并且打印姓名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<Author> authors = getAuthors();
authors.stream() // 把集合转化为流
.distinct()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() < 18;
}
})
.forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
  • Lambda 优化
1
2
3
4
authors.stream() // 把集合转化为流
.distinct()
.filter(author -> author.getAge() < 18)
.forEach(author -> System.out.println(author.getName()));

dubug 分析

img

  • 初始

img

  • 去重

img

  • 过滤

img

  • 打印

img

创建流

img

  • 数组
1
2
3
4
5
6
7
8
9
@Test
private static void test02() {
Integer[] arr = {1, 2, 3 ,4 ,5, 5};
Stream<Integer> stream = Arrays.stream(arr);
// Stream<Integer> stream1 = Stream.of(arr);
stream.distinct()
.filter(item -> item > 4)
.forEach(item -> System.out.println(item)); // 5
}
  • 双列集合
1
2
3
4
5
6
7
8
9
10
11
12
// 双列集合
@Test
public void testMap() {
HashMap<String, Integer> map = new HashMap<>();
map.put("诺手", 99);
map.put("亚索", 19);
map.put("ez", 32);
Set<Map.Entry<String, Integer>> entries = map.entrySet(); // 转化为单列
Stream<Map.Entry<String, Integer>> stream = entries.stream();
stream.filter(item -> item.getValue() > 90)
.forEach(item -> System.out.println(item.getValue())); // 99
}

中间操作

filter

1
2
3
4
5
6
7
8
9
10
11
@Test
void testFilter() {
List<Author> authors = getAuthors();
// 打印作家姓名长度大于1的作家
authors.stream()
.filter(item -> item.getName().length() > 1)
.forEach(item -> System.out.println(item.getName()));
}

// 蒙多
// 亚拉索

✨map

img

  • 无优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 打印所有姓名
@Test
void testMap() {
List<Author> authors = getAuthors();
authors.stream()
.map(new Function<Author, String>() {
@Override
// 参数map第一个泛型 返回值第二个泛型
public String apply(Author author) {
return author.getName();
}
})
.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
}

// 蒙多
// 亚拉索
// 易
// 易
  • 优化
1
2
3
authors.stream()
.map(author -> author.getName())
.forEach(s -> System.out.println(s));
  • debug map
  • 把作家的姓名作为流返回
  • 流中的数据类型从 Author 转为 String

img

distinct

  • 去重

img

  • 法一 重写 cmp
1
2
3
4
5
6
7
8
9
10
11
12
13
// 对年龄降序
@Test
void testsort() {
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
.forEach(item -> System.out.println(item.getAge()));
}

// 33
// 15
// 14
  • 法二空参 类需要重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Author implements Comparable<Author>{
private Long id;
private String name;
private Integer age;
// 简介
private String intro;
// 作品
private List<Book> books;

@Override
public int compareTo(Author o) {
return this.getAge() - o.getAge();
}
}

Limit

  • 取 limit 设置的前 n 个元素
1
2
3
4
5
6
7
8
9
10
// 去重 降序排序 打印最大年龄的两个作家
@Test
public void testLimit() {
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted()
.limit(2)
.forEach(item -> System.out.println(item.getAge()));
}

skip

  • 跳过流中前 n 个元素,返回剩下的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testSkip() {
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted()
.skip(1)
.forEach(item -> System.out.println(item.getAge()));
}

// 14 跳过
// 15
// 33

flatMap

  • map 只能将一个对象转为另一个对象作为流中的元素
  • flatMap 可以一个对象转为另多个对象作为流中的元素
  1. 优化前
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 打印所有书籍的名字 要求对重复的元素去重
@Test
void testFlatMap() {
List<Author> authors = getAuthors();
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.forEach(item -> System.out.println(item.getName()));
}
  1. 优化后
1
2
3
4
5
6
7
8
9
// 打印所有书籍的名字 要求对重复的元素去重
@Test
void testFlatMap() {
List<Author> authors = getAuthors();
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.forEach(item -> System.out.println(item.getName()));
}
  1. debug 分析

    1. authors流->books流

img

  1. 使用 split

img

  • 使用二

  • 分类格式 xxx,xxx

    • 要求获取所有分类,去重
    • 先得到书籍的流
    • 再得到分类数组(用 split 分开)
    • 转为 stream Arrays.stream
    • 去重分类

img

终结操作

forEach

img

count

1
2
3
4
5
6
7
8
9
10
11
12
// 打印所有作家的书籍综合
@Test
void testCount() {
List<Author> authors = getAuthors();
long count = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.count();
System.out.println(count);
}

// 3

max&min

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 获取作家书籍的最高分和最低分打印
@Test
void testMaxAndMin() {
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream()// Stream<Author>
.flatMap(author -> author.getBooks().stream()) // Stream<Book>
.map(book -> book.getScore())// Stream<Integer>
.max((b1, b2) -> b1 - b2);
System.out.println(max.get()); // 99
}


@Test
void testMaxAndMin() {
List<Author> authors = getAuthors();
Optional<Integer> min = authors.stream()// Stream<Author>
.flatMap(author -> author.getBooks().stream()) // Stream<Book>
.map(book -> book.getScore())// Stream<Integer>
.min((b1, b2) -> b1 - b2);
System.out.println(min.get()); // 70
}

✨collect

  • 作用 : 把当前流转换为一个集合
  1. 获取一个存放所有作者名字的 List 集合
1
2
3
4
5
6
7
8
9
// 获取一个存放所有作者名字的List集合
@Test
void testCollect() {
List<Author> authors = getAuthors();
List<String> nameList = authors.stream()
.map(author -> author.getName())
.collect(Collectors.toList());
System.out.println(nameList); // [蒙多, 亚拉索, 易, 易]
}
  1. 获取一个所有书名的 set 集合
1
2
3
4
5
6
7
8
9
10
@Test
void testSet() {
List<Author> authors = getAuthors();
Set<String> books = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getName())
.collect(Collectors.toSet());
System.out.println(books);
// [三国演义, 红楼梦, 西游记]
}
  1. 获取一个 Map 集合, map 的 key 为作者名,value 为List<Book>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Test
void testMap() {
List<Author> authors = getAuthors();
Map<String, List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
}, new Function<Author, List<Book>>() {
@Override
public List<Book> apply(Author author) {
return author.getBooks();
}
}));
System.out.println(map);
}

@Test
void testMap() {
List<Author> authors = getAuthors();
Map<String, List<Book>> map = authors.stream()
.distinct() // 一定要去重 map的key不能重复
.collect(Collectors.toMap(author -> author.getName(),
author -> author.getBooks()));
System.out.println(map);
}

{亚拉索=[Book(id=2, name=三国演义, category=社会, score=99, intro=三国演义)], 蒙多=[Book(id=1, name=西游记, category=冒险,奇幻, score=90, intro=西天取经)], 易=[Book(id=3, name=红楼梦, category=哲学, score=70, intro=ggg), Book(id=3, name=红楼梦, category=哲学, score=70, intro=ggg)]}

  • debug

img

匹配

anyMatch

可以用来判断任意符合匹配条件的元素,结果为 boolean 类型

  • 例:
1
2
3
4
5
6
7
8
9
@Test
void testAnyMatch() {
List<Author> authors = getAuthors();
boolean b = authors.stream()
.anyMatch(author -> author.getAge() > 29);
System.out.println(b);
}

// true

allMatch

所有都匹配返回 true

  • 例:判断所有作家是否都是成年人
1
2
3
4
5
6
7
8
//判断所有作家是否都是成年人
@Test
void testAllMatch() {
List<Author> authors = getAuthors();
boolean b = authors.stream()
.allMatch(author -> author.getAge() > 18);
System.out.println(b);
}

noneMatch

判断是否都不符合

  • 例:判断作家是否都没有超过 100 岁的
1
2
3
4
5
6
7
@Test
void testNoneMatch() {
List<Author> authors = getAuthors();
boolean b = authors.stream()
.noneMatch(author -> author.getAge() > 100);
System.out.println(b); // true
}

查找

findAny

获取流中的任意元素,无法保证是第一个

  • 例:获取任意一个年龄大于 18 的作家
1
2
3
4
5
6
7
8
9
@Test
void TestFindAny() {
List<Author> authors = getAuthors();
Optional<Author> optionalAuthor = authors.stream()
.filter(author -> author.getAge() > 18)
.findAny();
// 有数据输出 防止空指针异常
optionalAuthor.ifPresent(author -> System.out.println(author));
}

findFirst

获取流中的第一个元素

  • 例:获取一个年龄最小的作家
1
2
3
4
5
6
7
8
@Test
void testFindFirst() {
List<Author> authors = getAuthors();
Optional<Author> optionalFirst = authors.stream()
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.findFirst();
optionalFirst.ifPresent(author -> System.out.println(author.getAge())); // 14
}

reduce

  • 对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)

  • reduce 的作用是把 stream 中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算。

  • 例:求所有左值年龄和

1
2
3
4
5
6
7
8
9
10
 @Test
void testreduce() {
List<Author> authors = getAuthors();
Integer sum = authors.stream()
.distinct()
.map(author -> author.getAge())
// 0初始值 res 中间变量 element 流中元素
.reduce(0, (res, element) -> res + element);
System.out.println(sum);
}

img

  • 例:使用 reduce 求流中年龄最大的作者
1
2
3
4
5
6
7
8
9
10
@Test
void testrduce2() {
List<Author> authors = getAuthors();
Integer max = authors.stream()
.distinct()
.map(author -> author.getAge())
.reduce(Integer.MIN_VALUE, (res, element) ->
res = res < element ? element : res);
System.out.println(max); // 33
}

注意事项

img

Optional

概述

  • 防止出现空指针异常

img

使用

✨ 创建对象

  • Optional 就好像是包装类,可以把我们的具体数据封装 Optionalx 对象内部。然后我们去使用 Optional 中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。
  1. 我们一般使用 Optionall 的静态方法 ofNullable 来把数据封装成一个 Optional 对象。无论传入的参数是否为 nu 都不会出现问题.。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);
authorOptional.ifPresent(author1 -> System.out.println(author1));
}


static Author getAuthor() {
Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟哲理的祖安人", null);
return null;
}

// 源码
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
  1. 如果你确定一个对象不是空的则可以使用 Optionall 的静态方法 of 来把数据封装成 Optionali 对象。(不常用)
1
Optional<Author> authorOptional = Optional.of(author);

img

✨ 安全消费值

img

安全获取值

  • 不推荐使用get
  1. orElseGet

获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据!如果为空则根据你传入的参数来创建对象作为默认值返回。

1
2
3
4
5
6
7
8
9
10
11
12
@Test
void testOrElseGet() {
Optional<Author> authorOptional = getAuthorOptional();
Author author = authorOptional.orElseGet(() -> new Author(1L, "默认", 33, "默认", null));
System.out.println(author.getName());
}


static Optional<Author> getAuthorOptional() {
Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟哲理的祖安人", null);
return Optional.ofNullable(author);
}
  1. orElseThrow

获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。

1
2
3
4
5
6
7
8
9
10
11
@Test
void testOrElseThrow() {
Optional<Author> authorOptional = getAuthorOptional();
try {
Author author = authorOptional.orElseThrow(() ->
new RuntimeException("数据为null"));
System.out.println(author);
} catch (RuntimeException e) {
e.printStackTrace();
}
}

过滤

img

1
2
3
4
5
6
@Test
void testFilter() {
Optional<Author> authorOptional = getAuthorOptional();
authorOptional.filter(author -> author.getAge() > 18)
.ifPresent(author -> System.out.println(author));
}

判断

  • 是否存在

img

数据转换 Map

1
2
3
4
5
6
@Test
void testMap() {
Optional<Author> authorOptional = getAuthorOptional();
authorOptional.map(author -> author.getName())
.ifPresent(name -> System.out.println(name)); // 蒙多
}

函数式接口

概念

  • 只有一个抽象方法的接口我们称之为函数接口。
  • JDK 的函数式接口都加上了@Functionallnterface 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。

常见函数式接口

  1. consumer

img

  1. Function
  • 第一个泛型转为第二个泛型

img

  1. Predicate

img

  1. Supplier

img

函数式接口常用的默认方法

  1. and

img

  1. or

6.方法引用

  • 我们在使用 lambda 时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。

6.1 推荐用法

  • 我们在使用 lambda 时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。我们只需要在写完 lambda 方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
  • 当我们方法引用使用的多了慢慢的也可以直接写出方法引用。

6.2 基础格式

img

6.3 语法详解

引用类的静态方法

img

  • 调用了 String 类的抽象方法 且参数 age 传入 age

    • 优化
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
void testJingTai() {
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getAge()) // Stream<Integer>
.map(String::valueOf) // Stream<String>
.forEach(item -> System.out.println(item));
}

// 33
// 15
// 14
// 14

引用对象的实例方法

img

  • 优化
1
2
3
4
5
6
7
8
9
10
11
@Test
void testDuixiang() {
List<Author> authors = getAuthors();
StringBuilder sb = new StringBuilder();
authors.stream()
.map(author -> author.getName())
.forEach(sb::append);
System.out.println(sb);
}

// 蒙多亚拉索易易

引用类的实例方法

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MethodDemo {
interface UseString {
String use(String str, int start, int length);
}

public static String subAuthorName(String str, UseString useString) {
int start = 0;
int length = 1;
return useString.use(str, start, length);
}

public static void main(String[] args) {
subAuthorName("cyt", (str, start, length) -> str.substring(start, length));
// subAuthorName("cyt", String::substring);
}
}

构造器引用

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
void test1() {
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getName())
// .map(StringBuilder::new)
.map(name -> new StringBuilder(name))
.map(sb -> sb.append("-cyt").toString()) // 不是一行 不能使用引用
.forEach(str -> System.out.println(str));
}
// 蒙多-cyt
// 亚拉索-cyt
// 易-cyt
// 易-cyt


// 优化全部
authors.stream()
.map(Author::getName)
.map(StringBuilder::new)
.map(sb -> sb.append("-cyt").toString())
.forEach(System.out::println);

7.高级用法

基本数据类型优化

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
void testGaoJi() {
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getAge())
.map(age -> age + 10) // 装箱拆箱
.filter(age -> age > 18) // 装箱拆箱
.map(age -> age + 2) // 装箱拆箱
.forEach(System.out::println);
}

// 45
// 27
// 26
// 26

authors.stream()
.mapToInt(author -> author.getAge())
.map(age -> age + 10)
.filter(age -> age > 18)
.map(age -> age + 2)
.forEach(System.out::println);

并行流

  • 当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用 Stream 的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。

  • stream.parallel()parallel 方法可以把串行流转换成并行流。

1
2
3
4
5
6
7
8
@Test
void testJuc() {
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = integerStream.parallel() // 多个线程
.filter(num -> num > 5)
.reduce(0, (res, ele) -> res + ele);
System.out.println(sum); // 40
}
  • 使用 peek 调试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
void testJuc() {
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = integerStream.parallel()
.peek(new Consumer<Integer>() {
@Override
public void accept(Integer num) {
System.out.println(num + Thread.currentThread().getName());
}
})
.filter(num -> num > 5)
.reduce(0, (res, ele) -> res + ele);
System.out.println(sum); // 40
}
// 7main
// 6main
// 8main
// 1ForkJoinPool.commonPool-worker-11
// 3ForkJoinPool.commonPool-worker-9
// 4ForkJoinPool.commonPool-worker-4
// 2main
// 9ForkJoinPool.commonPool-worker-2
// 10ForkJoinPool.commonPool-worker-13
// 5ForkJoinPool.commonPool-worker-11
// 40