Java8
的出现可以说是 Java
面向函数式编程前进的一大步。刨除了很多冗余操作,间接的解放了双手👋
在看本篇文章时如果对 Java8的函数式接口不了解可以移步 Java8 Functional Interface写给自己的小白函数式接口说明
Java8 Stream
采用的是函数式编程方式,使用函数式编程方式在集合类上进行复杂操作的工具,可以更容易的使用 Lambda
表达式的形式书写,就看下流式操作正确的打开方式
对于不太清楚Stream流式操作的朋友而言,可以尽情 XX 了 😁
另附一张归结的思维导图,可以查看是否有自己需要的 API
1 流的创建
创建流的方式有很多种,比如:Arrays.stream
、Stream.of
、Collection.stream
、Stream.iterate
、Stream.generate
,流的创建方式有很多,比较常用的则是根据 Collection.stream()
,从集合中创建流
static <T> Stream<T> stream(T[] array)
static<T> Stream<T> of(T... values)
default Stream<E> stream()
static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
static<T> Stream<T> generate(Supplier<T> s)
第一种stream方法是Arrays.stream
| 第三种是Collection.stream
1.1 创建流方式示例
Arrays.stream
通过 Arrays
的静态方法来进行流的创建,参数为一个范型数组
String[] strArray = { "龙台", "百凯", "英雄" };
Arrays.stream(strArray).forEach(System.out::println);
// 顺序输出 龙台 百凯 英雄
Stream.of
传入一个泛型数组,或者多个参数,创建一个流,底层也是调用了 Arrays
的 stream
静态方法
String[] strArray = { "龙台", "百凯", "英雄" };
Stream.of(strArray).forEach(System.out::println);
// 顺序输出 龙台 百凯 英雄
Collection.stream
可以使用集合的接口的默认方法。使用这个方法,包括继承 Collection
的接口,如:Set,List,Map,SortedSet
等等都可以创建流
List<String> strList = Arrays.asList("龙台", "百凯","英雄");
strList.stream().forEach(System.out::println);
// 顺序输出 龙台 百凯 英雄
Stream.iterate
该方式是在 Stream
接口下的静态方法,见名识义可以看出是以迭代器的方式创建一个流
Stream.iterate(1, each -> each + 1)
.limit(3)
.collect(Collectors.toList())
.forEach(System.out::println);
// 顺序输出 1 2 3
Stream.generate
同样是 Stream
接口下的静态方法,参数就是一个 Supplier
的供给型的参数
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
// 依次打印5个随机数
2 Stream 流的处理顺序
Stream
流的系列流程可以总结分为以下操作
数据源(source) -> 数据转换/处理(中间操作) -> 结果处理(终端操作)
数据源指的就是通过以上几种方式生成的Stream
流。需要注意的是:终端操作一旦执行,那么整个流的生命周期就结束了
,无法再进行计算或再次汇总
❗️❗️❗️之前我也认为,会顺序输出 龙台 百凯
两个字符串,但是实际上是一个也不会输出,因为当前代码中 没有包含终端操作
Stream.of("龙台", "百凯")
.filter(each -> {
System.out.println(each);
return true;
});
如果说在👆代码加以点缀,那么就变得不一样了
Stream.of("龙台", "百凯")
.filter(each -> {
System.out.println("filter:" + each);
return true;
})
.forEach(each -> System.out.println("forEach:" + each));
}
filter:龙台
forEach:龙台
filter:百凯
forEach:百凯
之前一直认为是执行完filter
操作,然后才会执行接下来的forEach
👋👋👋的打脸
Stream
包含中间操作,这个在使用上也是一个坑,那就是流的 延迟性
,也叫做 惰性操作
实际上在处理 龙台
元素的时候,执行完 filter
就会执行 forEach
,然后继续执行 百凯
元素
查阅资料说明 这种设计的方式是出于性能考虑
Stream.of("龙台", "百凯", "英雄")
.map(each -> {
System.out.println("map: " + each);
return each;
})
.anyMatch(each -> {
System.out.println("anyMatch: " + each);
return each.startsWith("百"); // 过滤以 '百' 开头的元素
});
}
map: 龙台
anyMatch: 龙台
map: 百凯
anyMatch: 百凯
终端操作 anyMatch()
表示如果发现以 '百'
开头的元素,停止执行直接返回,所以 '英雄'
元素就没有被循环 ♻️ 执行
如果仅仅是上面的方式可能无法直观的看出性能差距在哪,接下来以 map
、filter
的方式来进行操作
Stream.of("龙台", "百凯", "驷马", "狂妄", "偏执", "英雄")
.map(each -> {
System.out.println("map: " + each);
return each;
})
.filter(each -> {
System.out.println("filter: " + each);
return each.startsWith("英");
})
.forEach(each -> System.out.println("forEach: " + each));
map: 龙台
filter: 龙台
map: 百凯
filter: 百凯
map: 驷马
filter: 驷马
map: 狂妄
filter: 狂妄
map: 偏执
filter: 偏执
map: 英雄
filter: 英雄
forEach: 英雄
通过打印得知,执行顺序分别是一次 map
、filter
,如果将 filter
和 map
的执行顺序转换一下的话
Stream.of("龙台", "百凯", "驷马", "狂妄", "偏执", "英雄")
.filter(each -> {
System.out.println("filter: " + each);
return each.startsWith("英");
})
.map(each -> {
System.out.println("map: " + each);
return each;
})
.forEach(each -> System.out.println("forEach: " + each));
filter: 龙台
filter: 百凯
filter: 驷马
filter: 狂妄
filter: 偏执
filter: 英雄
map: 英雄
forEach: 英雄
根据Stream
的执行方式来正确的定义函数API的顺序,如果集合包含大量元素的情况下,这种性能提升还是很可观的
值得一提的是Stream
的sorted
函数是水平执行的,会首先将集合中的内容全部在sorted
方法中执行后才会继续向下执行
3 数据处理/转换
一个流在被创建后,可以使用多个数据处理、转换操作,全部操作之后返回给终端操作的是一个新的流
3.1 流的映射 map/flatMap
在日常工作中通常会遇到这个场景,需要将一个集合对象中的某个属性提取出来重新组合成为一个新的集合,Stream
中的map
和flatMap
可以很好的完成这个需求,关键是:代码还很简洁 ❤️
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
map()
将学生集合中的学生名称抽取映射成为一个新的只包含名称的集合
@Data
public class Student {
private String num, name, area;
public Student(String num, String name, String area) {
this.area = area; this.name = name; this.num = num;
}
public static void main(String[] args) {
List<Student> studentList = Arrays.asList(
new Student("001", "东天北","山东"),
new Student("002", "心语祺","北京"),
new Student("003", "似无望","河南"));
List<String> nameList = studentList.stream()
.map(Student::getName)
.collect(Collectors.toList());
}
}
在这里先说明下map
和flatMap
的区别 ⬇️
flatmap
可以将一个二维的集合映射成一个一维,相当于他映射的深度比map
深了一层
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
List<Integer> collect = inputStream
.flatMap(each -> each.stream())
.collect(Collectors.toList());
collect.forEach(System.out::println);
对于流映射还包括mapToDouble
、mapToInt
、mapToLong
基本上相同的功能,有需求可以自己试下
3.2 流的过滤 filter
使用 filter 进行数据筛选,挑选出符合程序要求的元素
List<Integer> integerList = Arrays.asList(1,2,3,4,5,6,7,8);
integerList.stream().filter(each -> each > 6).forEach(System.out::println);
// filter条件 流中的元素必须 大于 6,符合要求的只有7和8 所以会被打印
3.3 流的去重 distinct
使用distinct
对数据流中的元素进行去重操作
在示例代码中,偏执
出现两次,在调用distinct
之后就会被去除
List<String> stringList = Arrays.asList("狂妄", "偏执", "英雄", "偏执");
stringList.stream().distinct().forEach(System.out::println);
3.4 流的排序 sorted
Stream
中对于排序使用的是sorted
对比与普通的集合、数组排序,Stream
的排序更会有优势,可以通过链式调用过滤、去重等方法筛选出结果数据流再进行排序,会更方便一些
@Data
public class Student {
private String num, name, area;
public Student(String num, String name, String area) {
this.area = area; this.name = name; this.num = num;
}
public static void main(String[] args) {
List<Student> studentList = Arrays.asList(
new Student("003", "英雄","河南"),
new Student("002", "偏执","北京"),
new Student("001", "狂妄","山东"));
List<Student> students = studentList.stream()
.sorted(((s1, s2) -> s1.getNum().compareTo(s2.getNum())))
.collect(Collectors.toList());
students.forEach(each -> System.out.println(each.toString()));
}
}
Student(num=001, name=狂妄, area=山东)
Student(num=002, name=偏执, area=北京)
Student(num=003, name=英雄, area=河南)
3.5 执行流 peek
为什么要叫peek
为执行流呢,之前我也是分不清这个有什么用,看了下他的接受参数就明白了 😄
peek
入参是一个Consumer
所以只会执行消费,而不会像map
一样将执行后的内容返回
那与forEach
的不同点在于 一个为中间操作
一个为终端操作
Stream<T> peek(Consumer<? super T> action);
peek
真的只是执行一下,再没有别的作用了,数据在执行peek
前和执行后 无差别
Stream.of("one", "two", "three", "four")
.filter(each -> each.length() > 3)
.peek(each -> System.out.println("peek:" + each))
.map(each -> "map:" + each)
.forEach(System.out::println);
3.6 截取和刨除 limit / skip
limit
和 数据库的分页道理是一致的,而skip
则是反其道而行。通过代码来进行查看
List<Integer> integerList = Arrays.asList(1,2,3,4,5,6);
integerList.stream().limit(3).forEach(System.out::println);
// 打印 1 2 3
integerList.stream().skip(3).forEach(System.out::println);
// 打印 4 5 6
4 流的终端操作
当流使用了终端操作之后,那么这个流的生命周期即为结束,如果再进行调用会编译异常或报错处理。在终端操作中会是对流的遍历操作
4.1 流的分组聚合 groupingBy / partitioningBy
流的聚合groupingBy
和mysql
数据库中的Group By
原理基本一致
List<Student> studentList = Arrays.asList(
new Student("003", "英雄","山东"),
new Student("002", "偏执","北京"),
new Student("001", "狂妄","山东"));
Map<String, List<Student>> stuGroup = studentList.stream()
.collect(Collectors.groupingBy(each -> each.getArea()));
stuGroup.forEach((key, val) -> System.out.println(key + ":" + val.toString()));
山东:[Student(num=003, name=英雄, area=山东), Student(num=001, name=狂妄, area=山东)]
北京:[Student(num=002, name=偏执, area=北京)]
而partitioningBy
则和普通Group By
显的有一丝不一致。详情查看代码
可以看的到 在进行分组时,返回参数的key
为Boolean
类型,在执行partitioningBy
时,如果地区为 “山东”则为True
List<Student> studentList = Arrays.asList(
new Student("003", "英雄","山东"),
new Student("002", "偏执","北京"),
new Student("001", "狂妄","山东"));
Map<Boolean, List<Student>> stuGroup = studentList.stream()
.collect(Collectors.partitioningBy(each -> Objects.equals(each.getArea(), "山东")));
System.out.println("结果为 true :" + stuGroup.get(true));
System.out.println("结果为 false :" + stuGroup.get(false));
结果为 true :[Student(num=003, name=英雄, area=山东), Student(num=001, name=狂妄, area=山东)]
结果为 false :[Student(num=002, name=偏执, area=北京)]
4.2 流的循环 forEach
forEach
接受一个Consumer
类型的消费函数 无返回,其实就是之前写的for、forEach
循环
void forEach(Consumer<? super T> action);
4.3 流的汇总 collect
一般而言,我们在使用Stream
的collect
方法时都是将流元素 转换为指定类型的集合
List<Integer> integerList = Arrays.asList(1,2,3,4,5).stream()
.collect(Collectors.toList());
5 short-circuiting
5.1 流数据的获取 findFirst
findFirst
通过API名称可以看出:获取的是流的第一个元素,返回值是一个Optional
,如果不清楚Optional
应用的可以参考博文 如何正确的使用Java 8的新特性之 Optional
// 如果流获取第一个元素为空,那么就会返回默认值
Optional<String> optional = Arrays.asList("偏执","狂妄","英雄").stream().findFirst();
System.out.println(optional.orElse("默认返回"));
5.2 流的匹配 anyMatch / allMatch / noneMatch
anyMatch
任何一个元素匹配,返回true
// 毫无疑问 当执行到元素 4 时,返回 **`true`**
boolean anyMatchResult = Arrays.asList(1,2,3,4,5).stream().anyMatch(each -> each > 3);
allMatch
所有元素匹配,返回true
// 因为不满足所有元素 > 3 所以返回值为false
boolean allMatchResult = Arrays.asList(1,2,3,4,5).stream().anyMatch(each -> each > 3);
noneMatch
没有一个元素匹配,返回true
boolean noneMatchResult = Arrays.asList(1,2,3,4,5).stream().anyMatch(each -> each > 10);
6 流的规约操作
6.1 流统计 summaryStatistics
流的统计这个功能在进行集合数据统计上来说是非常有用的,可以求平均值、最大值、最小值、个数、数据和
List<Integer> integerList = Arrays.asList(1,2,3,4,5,6);
IntSummaryStatistics iss = integerList.stream().mapToInt(each -> each).summaryStatistics();
iss.getMin(); iss.getAverage(); iss.getCount(); iss.getMax(); iss.getSum();
6.2 流统计 average、count、max、min、sum
average、count、max、min、sum
其实就相当与summaryStatistics
拆出的个体 一笔带过了
Stream<Integer> stream = Stream.of(1,2,3,4,5);
OptionalInt max = stream.mapToInt(each -> each).max();
OptionalLong min = stream.mapToLong(each -> each).min();
OptionalDouble average = stream.mapToInt(each -> each).average();
long count = stream.mapToInt(each -> each).count();
int sum = stream.mapToInt(each -> each).sum();
6.3 流的统计 count / counting
// 二者输出都为 3
List<String> stringList = Arrays.asList("偏执","狂妄","英雄");
long count = stringList.stream().count();
long counting = stringList.stream().collect(Collectors.counting());
7 总结
关于Stream
的内容大致就总结这么多吧,基本上一些常用项都已概括在本篇中
接下来会总结下并行流parallelStream
提供了流的并行处理,底层是使用了Fork/Join
框架实现