java8新特性专栏:https://blog.csdn.net/caoyuan666/category_11801993.html?spm=1001.2014.3001.5482
文章目录
0.总体介绍
0.1 stream流的概念
- Stream API 提供了一种高效且易于使用的处理数据的方式。Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作, 就类似于使用 SQL 执行的数据库查询。
- java中的流都是用于传输数据的数据渠道,但是不同的流具体功能不同,就像java中的IO流类似,stream流也是用于承载数据传输,但是stream流是为了处理数据。
- 既然stream流的核心目的是处理数据,那么对于无需前后数据就可处理的过程可利用多线程操作,本文主要讲stream的串行操作,并行操作放在了下一篇博客中。
0.2 steam流的三部分
- 创建 Stream:一个数据源(如: 集合、 数组), 获取一个流
- 中间操作:一个中间操作链, 对数据源的数据进行处理
- 终止操作(终端操作):一个终止操作, 执行中间操作链, 并产生结果
0.3 注意事项
- Stream 自己不会存储元素。
- Stream 不会改变源对象。 相反, 他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。 这意味着他们会等到需要结果的时候才执行。
1. 创建方式
四种构造方法:
@Test
public void test7() {
// 1.通过Collection集合的方法获取
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
// 2.数组:通过Arrays的静态方法stream()
String[] strArr = new String[2];
Stream<String> stream1 = Arrays.stream(strArr);
// 3.通过Stream的静态方法of()
Stream<String> a = Stream.of("a", "b", "c");
// 4.创建无限流
// 4.1 迭代
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 1);
stream2.limit(5).forEach(System.out::print);
System.out.println();
// 4.2 生成
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
}
前3种应该比较好理解,个人认为最常见的还是第一种,对于无限流,创建时写入创建元素的方法,那么流的长度就是无穷大,为了功能需求,一般通过中间操作limit()来实现限制流的长度:
- 迭代:Stream.iterate() 第一个参数为第一个元素大小,第二个参数为后一个元素在前一个元素已知条件下的生成规则
- 生成:生成方法
2.中间操作
位置 | 类型 | 函数 | 作用 |
---|---|---|---|
中间操作 | 无状态 | filter() | 过滤 |
中间操作 | 无状态 | peek() | 流中的执行操作,debug,不改变元素内容 |
中间操作 | 无状态 | map() | 元素->元素 映射 |
中间操作 | 无状态 | FlatMap() | 元素->流 映射,并将流中的元素合并到原数据流中 |
中间操作 | 无状态 | mapToInt() | 元素->IntStream 映射 |
中间操作 | 无状态 | mapToDouble() | 元素->DoubleStream 映射 |
中间操作 | 无状态 | mapToLong() | 元素->LongStream 映射 |
中间操作 | 无状态 | flatMapToInt() | 元素->IntStream 映射,和原数据流合并 |
中间操作 | 无状态 | flatMapToDouble() | 元素->DoubleStream 映射,和原数据流合并 |
中间操作 | 无状态 | flatMapToLong() | 元素->LongStream 映射,和原数据流合并 |
中间操作 | 有状态 | limit(n) | 切片:保留前n个元素 |
中间操作 | 有状态 | skip(n) | 切片:丢弃前n个元素 |
中间操作 | 有状态 | distinct() | 筛选:去重,hashCode()&equals() |
中间操作 | 有状态 | sorted() | 排序 |
中间操作 | 有状态 | sorted(comp) | 按比较器comp排序 |
- 无状态: 指元素的处理不受之前元素的影响,各个元素的处理可多线程操作;
- 有状态: 指该操作只有拿到所有元素之后才能继续下去。
下面的程序主要用下面这个流进行举例:
Stream<String> stream1 = Stream.iterate("a", (x) -> {
char[] chars = x.toCharArray();
return x + (char) (chars[chars.length - 1] + 1);
}).limit(6);
stream1.forEach(System.out::println);
输出:
a
ab
abc
abcd
abcde
abcdef
2.1无状态操作
2.1.1 过滤
- filter(Predicate p) 接收 Lambda , 从流中保留符合条件的元素。
stream1.filter(x -> x.length() > 3).forEach(System.out::println);
结果:
abcd
abcde
abcdef
2.1.2 执行
执行操作主要用于debug中使用,和forEach作用类似,对每个元素遍历操作且不改变元素内容,但forEach是结束流只能在最后用一次,所以中间的操作使用peek()
- peek(Consumer<? super T> action) 用于流中间不改变元素内容的操作
为更好的举例,这里使用int类型的流举例,可以看出流中执行的顺序:
List<Integer> list = Arrays.asList(4, 7, 9, 11, 12);
list.stream()
.peek(x -> System.out.println("stream: " + x))
.map(x -> x + 2)
.peek(x -> System.out.println("map: " + x))
.filter(x -> x % 2 != 0)
.peek(x -> System.out.println("filter: " + x))
.limit(2)
.peek(x -> System.out.println("limit: " + x))
.collect(Collectors.toList());
stream: 4
map: 6
stream: 7
map: 9
filter: 9
limit: 9
stream: 9
map: 11
filter: 11
limit: 11
Process finished with exit code 0
2.1.3 映射
- map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
- flatMap(Function f) 接收一个函数作为参数, 将流中的每个值都换成另一个流, 然后把所有流连接成一个流
- mapToDouble(ToDoubleFunction f) 接收一个函数作为参数, 该函数会被应用到每个元素上, 产生一个新的 DoubleStream。
- mapToInt(ToIntFunction f) 接收一个函数作为参数, 该函数会被应用到每个元素上, 产生一个新的 IntStream。
- mapToLong(ToLongFunction f) 接收一个函数作为参数, 该函数会被应用到每个元素上, 产生一个新的 LongStream。
map:用于数据处理,这里相当于大数据map-reduce中map处理,返回类型可以与输入类型不同,在结束操作中有reduce方法
stream1.map(x -> x+"111").forEach(System.out::println);
a111
ab111
abc111
abcd111
abcde111
abcdef111
flatMap:在flatMap中,返回的数据类型是一个stream流类型,这个方法将流中的每个元素打平到原来的大流中,类似于集合的addAll()方法
stream1.flatMap(x -> {
char[] chars = x.toCharArray();
Character[] newChars = new Character[chars.length];
for (int i = 0; i < chars.length; i++) {
newChars[i] = chars[i];
}
Stream<Character> stream2 = Arrays.stream(newChars);
return stream2;
}).forEach(System.out::print);
aababcabcdabcdeabcdef
2.2 有状态操作
有状态操作需要将所用元素执行完前面的操作后,再一起执行
2.2.1 切片
- limit(long maxSize) 截断流, 使其元素不超过给定数量。
- skip(long n) 跳过元素, 返回一个扔掉了前 n 个元素的流。 若流中元素不足 n 个, 则返回一个空流。 与 limit(n) 互补
2.2.2 筛选去重
- distinct() 筛选, 通过流所生成元素的 hashCode() 和 equals() 去除重复元素
2.2.3 排序
- sorted() 产生一个新流, 其中按自然顺序排序
- sorted(Comparator comp) 产生一个新流, 其中按比较器顺序排序
3.终止操作(结束操作)
位置 | 类型 | 函数 | 作用 |
---|---|---|---|
结束操作 | 短路操作 | findFirst() | 返回第一个元素 |
结束操作 | 短路操作 | findAny() | 返回任意一个元素 |
结束操作 | 短路操作 | allMatch() | 是否匹配所有元素 |
结束操作 | 短路操作 | anyMatch() | 是否至少匹配一个元素 |
结束操作 | 短路操作 | noneMatch() | 是否没有匹配所有元素 |
结束操作 | 短路操作 | noneMatch() | 是否没有匹配所有元素 |
结束操作 | 非短路操作 | reduce() | 规约合并到一个元素 |
结束操作 | 非短路操作 | collect() | 收集到其他形式 |
结束操作 | 非短路操作 | forEach() | 内部迭代 |
结束操作 | 非短路操作 | toArray() | 转化为数组 |
结束操作 | 非短路操作 | max() | 统计:最大值 |
结束操作 | 非短路操作 | min() | 统计:最小值 |
结束操作 | 非短路操作 | count() | 统计:元素数量 |
- 非短路操作: 指必须处理所有元素才能得到最终结果;
- 短路操作: 指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
程序示例:对于结束操作,涉及到大量属性特征的处理,所以创建了一个book类,如下所示,类型包含:学习&小说
class Book{
private String name;
private int page;
private Type type;
public Book() {
}
public Book(String name, int page, Type type) {
this.name = name;
this.page = page;
this.type = type;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", page=" + page +
", type=" + type +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return page == book.page && Objects.equals(name, book.name) && type == book.type;
}
@Override
public int hashCode() {
return Objects.hash(name, page, type);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
}
enum Type {
STUDY,
NOVEL
}
使用的流如下所示:
@Test
public void test10(){
List<Book> books = new ArrayList<>();
books.add(new Book("Spring", 200, Type.STUDY));
books.add(new Book("Python", 180, Type.STUDY));
books.add(new Book("C++", 230, Type.STUDY));
books.add(new Book("NOVEL1", 230, Type.NOVEL));
books.add(new Book("NOVEL2", 280, Type.NOVEL));
books.add(new Book("NOVEL3", 180, Type.NOVEL));
books.stream().forEach(System.out::println);
}
Book{name='Spring', page=200, type=STUDY}
Book{name='Python', page=180, type=STUDY}
Book{name='C++', page=230, type=STUDY}
Book{name='NOVEL1', page=230, type=NOVEL}
Book{name='NOVEL2', page=280, type=NOVEL}
Book{name='NOVEL3', page=180, type=NOVEL}
3.1 短路操作
短路操作再遇到某些符合条件的元素时可提前结束,得到结果,无需遍历所有元素
3.1.1 查找
- findFirst() 返回第一个元素
- findAny() 返回当前流中的任意元素
3.1.2 匹配
- allMatch(Predicate p) 检查是否匹配所有元素
- anyMatch(Predicate p) 检查是否至少匹配一个元素
- noneMatch(Predicate p) 检查是否没有匹配所有元素
// 1.查找
Optional<Book> first = books.stream().findFirst();
System.out.println(first.get());
Optional<Book> any = books.stream().findAny();
System.out.println(any.get());
// 2.匹配
boolean b = books.stream().allMatch(x -> x.getPage() > 200);
System.out.println(b);
boolean b1 = books.stream().anyMatch(x -> x.getType().equals(Type.STUDY));
System.out.println(b1);
boolean b2 = books.stream().noneMatch(x -> x.getPage() > 200);
boolean b3 = books.stream().noneMatch(x -> x.getPage() < 100);
System.out.println(b2 + " " + b3);
Book{name='Spring', page=200, type=STUDY}
Book{name='Spring', page=200, type=STUDY}
false
true
false true
3.2 非短路操作
3.2.1 规约
规约reduce,相当于大数据map-reduce模型中的reduce,将之前map之后各个元素结果结合到一起
- reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来, 得到一个值。返回 T。iden为初始元素,b为二元运算,结合各个元素,由于有初始元素iden,所以即使流为空,也可以返回iden
- reduce(BinaryOperator b) 可以将流中元素反复结合起来, 得到一个值。返回 Optional
Integer allPage = books.stream().map(Book::getPage).reduce(100, (a, b) -> a + b);
System.out.println(allPage);
List<Book> books2 = new ArrayList<>();
Integer allPage1 = books2.stream().map(Book::getPage).reduce(200, (a, b) -> a + b);
System.out.println(allPage1);
Optional<Integer> allPage2 = books.stream().map(Book::getPage).reduce((a, b) -> a + b);
System.out.println(allPage2.get());
1400
200
1300
3.2.2 收集
- collect(Collector c)将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
- Collectors实用类提供了很多静态方法, 可以方便地创建常见Collector接口的收集器实例
Collectors的常用静态方法:
- 收集为Collection集合
- toList(),将流收集到List,返回类型为List
- toSet(),把流中元素收集到Set,返回类型为Set
- toCollection(Supplier sup),把流中元素收集到创建的集合,可用于任何其他的集合类型
- 收集到统计特征
- counting():计算流中元素的个数,返回Long
- summingInt(Function fun):对流中元素的整数属性求和,返回Integer
- averagingInt(Function fun):计算流中元素Integer属性的平均值,返回Double
- summarizingInt(Function fun):收集流中Integer属性的统计值。如: 平均值,返回IntSummaryStatistics
- 连接符:joining(),连接流中每个字符串,返回String
- 比较器统计
- maxBy(Comparator com):根据比较器选择最大值,返回Optional
- minBy(Comparator com):根据比较器选择最小值,返回Optional
- 规约:reducing():从一个作为累加器的初始值开始, 利用BinaryOperator与流中元素逐个结合, 从而归约成单个值
- 分组&分区
- 分组groupingBy(Function fun):根据某属性值对流分组, 属性为K, 结果为V,返回Map<K, List>,可以级联实现多级分组
- 分区:partitioningBy(Function fun),根据true或false进行分区,Map<Boolean, List>
- collectingAndThen():包裹另一个收集器, 对其结s果转换函数
// 1.集合
List<Book> collect = books.stream().limit(3).collect(Collectors.toList());
System.out.println(collect);
Set<Book> collect1 = books.stream().limit(3).collect(Collectors.toSet());
System.out.println(collect1);
ArrayList<Book> collect2 = books.stream().limit(3).collect(Collectors.toCollection(ArrayList::new));
System.out.println(collect2);
// 2.统计
Long collect3 = books.stream().limit(3).collect(Collectors.counting());
System.out.println(collect3);
IntSummaryStatistics collect4 = books.stream().limit(3).collect(Collectors.summarizingInt(Book::getPage));
System.out.println(collect4.getMax());
System.out.println(collect4.getSum());
System.out.println(collect4.getAverage());
// 3.连接
String collect5 = books.stream().map(Book::getName).collect(Collectors.joining(",", "+++", "---"));
System.out.println(collect5);
// 4.分组
Map<Type, List<Book>> collect6 = books.stream().collect(Collectors.groupingBy(Book::getType));
System.out.println(collect6);
Map<Type, Map<Boolean, List<Book>>> collect7 = books.stream()
.collect(Collectors.groupingBy(Book::getType, Collectors.groupingBy(x -> x.getPage() > 200)));
System.out.println(collect7);
[Book{name='Spring', page=200, type=STUDY}, Book{name='Python', page=180, type=STUDY}, Book{name='C++', page=230, type=STUDY}]
[Book{name='Python', page=180, type=STUDY}, Book{name='C++', page=230, type=STUDY}, Book{name='Spring', page=200, type=STUDY}]
[Book{name='Spring', page=200, type=STUDY}, Book{name='Python', page=180, type=STUDY}, Book{name='C++', page=230, type=STUDY}]
3
230
610
203.33333333333334
+++Spring,Python,C++,NOVEL1,NOVEL2,NOVEL3---
{NOVEL=[Book{name='NOVEL1', page=230, type=NOVEL}, Book{name='NOVEL2', page=280, type=NOVEL}, Book{name='NOVEL3', page=180, type=NOVEL}], STUDY=[Book{name='Spring', page=200, type=STUDY}, Book{name='Python', page=180, type=STUDY}, Book{name='C++', page=230, type=STUDY}]}
{NOVEL={false=[Book{name='NOVEL3', page=180, type=NOVEL}], true=[Book{name='NOVEL1', page=230, type=NOVEL}, Book{name='NOVEL2', page=280, type=NOVEL}]}, STUDY={false=[Book{name='Spring', page=200, type=STUDY}, Book{name='Python', page=180, type=STUDY}], true=[Book{name='C++', page=230, type=STUDY}]}}
Process finished with exit code 0
3.2.3 迭代
- forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。 相反, Stream API 使用内部迭代——它帮你把迭代做了)
3.2.4 数组
- toArray():转为数组
Object[] objects = books.stream().map(Book::getName).toArray();
System.out.println(Arrays.toString(objects));
[Spring, Python, C++, NOVEL1, NOVEL2, NOVEL3]
3.2.5 统计
- count() 返回流中元素总数
- max(Comparator c) 返回流中最大值
- min(Comparator c) 返回流中最小值