前言
在jdk8环境中, 演示Stream的基本操作; 读者需要了解Lambda表达式的用法, 作为阅读本文的预备知识.
本文内容基本借鉴于 慕课网, 感谢!
Stream 简介
java8中, 流的操作基本分三部分:
- 构建流: 流的来源可能是集合, 数组, 文件, 其他来源暂不讨论
- 中间操作: 可以从原始的流开始, 执行多次, 每次执行返回一个新的流
- 终端操作: 只能在流的最后, 执行一次的操作
注意以下几点:
- 流不存储或改变其元素, 只返回新流或执行流中定义的操作
- 流操作是惰性的, 只有终端操作被执行时, 其中间操作才执行
- 所有流操作以函数式接口(lambda表达式) 作为参数
构建流
1.由数值或对象直接构建流:
@Test
public void streamFromValue() {
Stream stream1 = Stream.of(1, 2, 3, 4, 5);
Stream stream2 =Stream.of(90, -2L, 2d,"22",new Integer(8), new Date());
// Stream<Integer> stream3 =Stream.of(90, -2L, 2d,"22",new Integer(8), new Date());
stream1.forEach(System.out::println);
System.out.println("-------------");
stream2.forEach(System.out::println);
}
结果:
1
2
3
4
5
-------------
90
-2
2.0
22
8
Sat Nov 09 19:04:09 CST 2019
注意到, 可以给流添加泛型, 而统一流元素的类型; 不加泛型时, 可传入任何类型
2.由数组构建流:
@Test
public void streamFromArray() {
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
stream.forEach(System.out::println);
}
观察Arrays.stream方法的源码:
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}
可以看到, 数组元素的类型和它所生成流的泛型保持一致;
如果数组是基本类型的, 所生成流的泛型是相应的包装类
3.通过文件生成流
@Test
public void streamFromFile() throws IOException {
// TODO 此处替换为本地文件的地址全路径
String filePath = "";
Stream<String> stream = Files.lines(
Paths.get(filePath));
stream.forEach(System.out::println);
}
这个不多说了, 试下就知道
4.通过集合生成流(常见)
jdk8的Collection类中, 有一个default 的stream方法:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
可以看到, 任何集合通过stream() 方法, 即可获得拥有相同泛型的流; 这个用法后面演示会涉及, 就不赘述了
准备一个用于测试的集合
再贴一下 演示的github链接, 在sku包里都有.
看一下这个集合的全貌:
@Test
public void all(){
list.stream()
.forEach(System.out::println);
}
结果是9个购物车中的商品:
有状态操作(串行) 和无状态操作(并行)的区别
有状态操作, 就是需要收集流中所有元素后, 才能正确完成的操作(官方说法是: 需要内部状态来累计计算结果
); 无状态操作, 就是除了有状态操作的其他中间操作, 也可以理解成, 只依赖当前元素, 就能正确完成的操作.
因此, distinct()去重, sorted() 排序, limit(int i) 截断只留前 i个, skip(int i) 跳过前 i个, 都是不可能依赖单个元素能正确完成的; 余下的中间流都属于无状态的
可能有人对filter() 有疑问, 其实 filter() 是逐个判断流元素是否符合断言, 从而达到筛选效果, 可以理解成"筛出来一个, 就放行一个".
演示下有状态操作 sort(), 这里的演示中, 读者不必在意流参数组织的形式, 而是注意有状态操作和无状态操作的区别:
@Test
public void sortedAll() {
list.stream()
.peek(sku -> System.out.println(sku.getSkuId()))
.sorted(Comparator.comparing(sku -> sku.getSkuId()))
.forEach(sku -> System.out.println("__" + sku.getSkuId() + "__"));
}
可以看到, SkuId在执行了sort()前先被peek()输出, 然后执行sort()后, 正确地从小到大输出
演示下无状态操作, 注意和上面的有状态操作的对比:
@Test
public void filterTest() {
list.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.filter(sku -> null != sku)
.forEach(sku -> System.out.println("__" + sku.getSkuName() + "__"));
}
显然, 这次不是整个流都执行完peek(), 再执行filter()了. 这里peek()和 filter()都是无状态流, 是并行流. 它们执行完一个元素就放行一个; 相反, 串行流执行完整个流, 再放行整个流
常用无状态操作(并行操作)演示
filter(Predicate<? super T> predicate)
打开看Stream接口源码中的 filter, 可以看到filter需要参数为断言型接口, 返回值仍是Stream类型的
@Test
public void filterTest2() {
list.stream()
.peek(sku -> System.out.println(sku.getSkuId()))
.filter(sku -> sku.getSkuCategory().equals(SkuCategoryEnum.BOOKS))
.forEach(System.out::println);
}
从演示结果中, 看到filter操作是并行操作, 且将符合断言型接口的元素, 放置到返回的流中
map(Function<? super T, ? extends R> mapper)
map的参数是一个函数式接口, 其功能是, 将一个元素改变后, 放入作为返回值的流; 因此map具有转换流的作用
@Test
public void mapTest() {
list.stream()
.peek(sku -> System.out.println(sku.getSkuName())) //简单输出下元素, 但经过此操作, 流不会改变
.map(sku -> "__" + sku.getSkuName() + "__")
.forEach(System.out::println);
}
可以看到, map(Function<? super T, ? extends R> mapper) 依然是并行操作
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
flatMap也是转换流元素的中间操作, 但是它可以把一个元素转换成多个元素的组成的流; 传入参数依然是函数式接口
@Test
public void flatMapTest() {
list.stream()
.peek(sku -> System.out.println()) //空输出, 只用作换行
.peek(sku -> System.out.println(sku.getSkuName()))
.flatMap(sku -> Arrays.stream(sku.getSkuName().split("")))
.forEach(ch -> System.out.print(ch +"\t"));
}
可以看到, flatMap是并行操作, 它把接收到的单个元素转换成流返回
peek(Consumer<? super T> action)
peek的功能经过前几个例子, 大家应该很熟悉了, 其特点是针对流进行指定操作, 操作后流不会被改变.
但是这样说并不确切, 因为peek是典型的无状态操作, 每次只针对一个元素. 实际上对其他无状态操作, 如 filter, map, 都是改变当前操作元素, 并放到作为返回值的流中, 以至于看上去整个流被改变了; peek是直接把当前元素放到作为返回值的流中, 所以看上去流没有改变.
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
有一类特殊的中间操作, 可以把输入元素转换成指定类型元素. 相当于指定返回元素
类型的map操作
@Test
public void mapTest1(){
list.stream()
.peek(sku -> System.out.println(sku.getSkuId()))
// .mapToDouble(sku -> sku.getSkuName()) //转换成错误的类型, 编译报错
.mapToDouble(sku -> sku.getSkuId())
.forEach(System.out::println);
}
常用有状态操作(串行操作)演示
sorted(Comparator<? super T> comparator) 和 sorted()
@Test
public void sortTest() {
list.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.sorted(Comparator.comparing(Sku::getSkuId).reversed())
.forEach(System.out::println);
}
看结果可知, sorted(Comparator<? super T> comparator) 方法根据传入的Comparator接口, 对流元素进行排序, 使用 reversed() 可以指定逆序排序
再看 sorted() 操作:
@Test
public void sortTest1(){
list.stream()
.map(sku -> sku.getSkuPrice())
.sorted() // 等同于 sorted((c1, c2) -> c1.compareTo(c2))
.forEach(System.out::println);
}
注意sorted() 接收的流元素所在的类, 必须实现Comparable 接口, 否则运行时, 无法知道比较的依据:
@Test
public void sortTest2(){
list.stream()
.sorted() // 等同于 sorted((c1, c2) -> c1.compareTo(c2))
.forEach(System.out::println);
}
distinct() 去重操作
@Test
public void distinctTest() {
list.stream()
.map(sku -> sku.getSkuCategory())
.distinct()
.forEach(System.out::println);
}
skip(long n), 对整个流跳过指定的前几个元素, 再返回
@Test
public void skipTest() {
list.stream()
.sorted(Comparator.comparing(sku -> sku.getTotalPrice()))
.skip(3)
.forEach(System.out::println);
}
按总价排序, 再跳过总价最低的前三个
limit(long maxSize), 截取整个流的前若干个元素, 返回
@Test
public void limitTest() {
list.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice))
.skip(2 * 3)
// limit
.limit(3)
.forEach(System.out::println);
}
可以看到, skip(long n)结合 limit(long n)使用, 可以模拟分页效果
常用终端操作演示
boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)
@Test
public void allMatchTest() {
boolean match = list.stream()
.peek(System.out::println)
.allMatch(sku -> sku.getTotalPrice() > 100);
System.out.println(match);
}
可以看到, allMatch(Predicate<? super T> predicate) 根据断言型接口筛选元素, 都匹配为true, 如果遇到不匹配的第一个元素, 立即短路(不再选取下一个元素), 并返回false
anyMatch(Predicate<? super T> predicate), 任何一个匹配就短路, 返回true; noneMatch(Predicate<? super T> predicate), 任何一个匹配就短路, 返回 false, 直接把测试代码贴在下面:
@Test
public void anyMatchTest() {
boolean match = list.stream()
.peek(System.out::println)
.anyMatch(sku -> sku.getTotalPrice() > 100);
System.out.println(match);
}
@Test
public void noneMatchTest() {
boolean match = list.stream()
.peek(System.out::println)
.noneMatch(sku -> sku.getTotalPrice() > 3000);
System.out.println(match);
}
collect() 方法演示
collect() 方法可以把流变成集合返回, 也可以把流按条件分组, 变成Map返回, 下面是两个示例, 了解就可以
@Test
public void toList() {
List<Sku> list = CartService.getCartSkuList();
List<Sku> result = list.stream()
.filter(sku -> sku.getTotalPrice() > 100)
.collect(Collectors.toList());
result.stream()
.forEach(System.out::println);
}
@Test
public void group() {
List<Sku> list = CartService.getCartSkuList();
Map<Object, List<Sku>> group = list.stream()
.collect(Collectors.groupingBy(
sku -> sku.getSkuCategory())); //以类别为条件, 为list分组
for(Map.Entry entry: group.entrySet()){
System.out.println(entry.getKey());
((List)entry.getValue()).stream()
.forEach(System.out::println);
}
}