Stream 管道流

前言

在 java8 中添加了一个新的抽象,称之为 Stream,可以让程序员以一种声明式的方式处理数据,Stream流的处理方式,可以把要处理的元素看成一种流,流在管道中传输,并且在管道的节点上进行处理。

Stream 的操作可以分为两大类:中间操作、终结操作

中间操作可分为:

  • 无状态(Stateless)操作:指元素的处理不受之前元素的影响。
  • 有状态(Stateful)操作:指该操作只有拿到所有元素之后才能继续下去。

终结操作可分为:

  • 短路(Short-circuiting)操作:指遇到某些符合条件的元素就可以得到最终结果。
  • 非短路(Unshort-circuiting)操作:指必须处理完所有元素才能得到最终结果。

在这里插入图片描述

Stream Api

1、流的创建

  1. 通过 java.util.Collection.stream() 方法用集合创建流
List<String> list = Arrays.asList("hello", "world", "stream");
// 创建顺序流
Stream<String> stream = list.stream();
// 创建并行流
Stream<String> parallelStream = list.parallelStream();
  1. 使用 java.util.Arrays.stream(T[] array) 方法用数组创建流
String[] array = {"h", "e", "l", "l", "o"};
Stream<String> arrayStream = Arrays.stream(array);
  1. Stream的静态方法:of()、iterate()、generate()
// 这个方法不但支持将数组转成 Stream,也支持传入多个参数,将参数最终转成 Stream,其实 Stream.of() 也是调用的 Arrays.stream() 方法来实现的。
Integer[] array = new Integer[] {1, 2, 3};
Stream stream1 = Stream.of(array);
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5, 6);

// Stream 接口有两个用来创建无限 Stream 的静态方法。generate() 方法接受一个参数函数,可以使用类似如下代码来创建一个你需要的 Stream。
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);

// Stream 接口的另一用来创建无限 Stream 的静态方法就是 iterate() 方法。iterate() 方法也是接受一个参数函数,可以用类似如下代码来创建一个你需要的 Stream。
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2).limit(3);

串行流和并行流区分

流有串行和并行两种,串行流上的操作是在一个线程中依次完成,而并行流则是在多个线程上同时执行。并行与串行的流可以相互切换:通过 stream.sequential() 返回串行的流,通过 stream.parallel() 返回并行的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。

在这里插入图片描述

2、中间操作

2.1、有状态

① distinct
  • distinct:去除重复元素。
    在这里插入图片描述
    样例:
Stream<String> stream = Stream.of("1", "2", "2", "1", "7", "9");
stream.distinct().forEach(System.out::println);// 1 2 7 9

那么如果需要对自定义的对象进行过滤,则需要重写对象的equals方法,另外有一个细节可以看到,去重之后还是按照原流中的排序顺序输出的,所以是有序的。

② sorted
  • sorted:自然排序,流中元素需实现 Comparable 接口。

该接口有两种形式:无参和有参数

Stream<T> sorted();
 
Stream<T> sorted(Comparator<? super T> comparator);

那区别其实就在于:传入比较器的参数,可以自定义这个比较器,即自定义比较规则。

Stream<String> stream = Stream.of("1", "2", "2", "1", "7", "9");
stream.sorted().forEach(System.out::println);// 1 1 2 2 7 9
③ limit
  • limit:获取 n 个元素。
    在这里插入图片描述
    样例:
Stream<String> stream = Stream.of("1", "2", "2", "1", "7", "9");
stream.limit(3).forEach(System.out::println);// 1 2 2
④ skip
  • skip:跳过 n 元素,配合 limit(n)可实现分页。

在这里插入图片描述
样例:

Stream<String> stream = Stream.of("1", "2", "2", "1", "7", "9");
stream.skip(3).forEach(System.out::println);// 1 7 9
⑤ concat
  • concat:如果有两个流,希望合并为一个流,那么可以使用Stream接口的静态方法concat。

样例:

// 创建一个Stream流
Stream<String> stream1 = Stream.of("1", "2", "3");
// 获取一个Stream流
String[] arr = {"11", "12", "13"};
Stream<String> stream2 = Stream.of(arr);
// 把以上两个流组合为一个流
Stream<String> concat = Stream.concat(stream1, stream2);
// 遍历concat流
concat.forEach(i -> System.out.println(i));

2.2、无状态

① filter
  • filter:过滤流中的某些元素。
    在这里插入图片描述
    样例:
List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2);
Stream<Integer> stream = list.stream();
stream.filter(x -> x > 5).forEach(System.out::println);// 6 7 8
② map
  • map:将其映射成一个新的元素。

在这里插入图片描述
样例:

List<Integer> integerList = Lists.newArrayList();
integerList.add(15);
integerList.add(32);
integerList.add(5);
// 将Integer类型转换成String.类型
List<String> afterString = integerList.stream().map(i -> String.valueOf(i)).collect(Collectors.toList());// [15, 32, 5]
③ flatMap
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
    在这里插入图片描述
    样例:
List<String> list1 = Arrays.asList("m,k,l,a","1,3,5,7");
List<String> listNew = list1.stream().flatMap(s -> {
    // 将每个元素转换成一个stream
    String[] split = s.split(",");
    Stream<String> s2 = Arrays.stream(split);
    return s2;
}).collect(Collectors.toList());

System.out.println("处理前的集合:" + list1);// [m,k,l,a, 1,3,5,7]
System.out.println("处理后的集合:" + listNew);// [m, k, l, a, 1, 3, 5, 7]
④ peek
  • peek:peek 操作接收的是一个 Consumer 函数。顾名思义 peek 操作会按照 Consumer
    函数提供的逻辑去消费流中的每一个元素,同时有可能改变元素内部的一些属性。

在这里插入图片描述
样例:

Stream<String> stream = Stream.of("hello", "felord.cn");
stream.peek(System.out::println);// 空

执行之后,控制台并没有输出任何字符串,这是因为流的生命周期有三个阶段:

  • 起始生成阶段。
  • 中间操作会逐一获取元素并进行处理。可有可无。所有中间操作都是惰性的,因此,流在管道中流动之前,任何操作都不会产生任何影响。
  • 终端操作。通常分为 最终的消费 (foreach 之类的)和 归纳 (collect)两类。还有重要的一点就是终端操作启动了流在管道中的流动。

所以,上面的代码是因为缺少了终端操作,因此,我们改成如下即可:

Stream<String> stream = Stream.of("hello", "felord.cn");
stream.peek(System.out::println).collect(Collectors.toList());// hello felord.cn

重点:peek VS map

peek 操作 一般用于不想改变流中元素本身的类型或者只想元素的内部状态时;

而 map 则用于改变流中元素本身类型,即从元素中派生出另一种类型的操作。

⑤ mapToInt、mapToLong、mapToDouble、flatMapToDouble、flatMapToInt、flatMapToLong
  • 以上这些操作是map和flatMap的特例版,也就是针对特定的数据类型进行映射处理。

样例:

Stream<String> stream = Stream.of("hello", "felord.cn");
stream.mapToInt(s->s.length()).forEach(System.out::println);// 5 9

并且这些指定类型的流,还有另外一些常用的方法,也是很好用的,可以参考:IntStream、LongStream、DoubleStream。

⑥ unordered
  • unordered:操作不会执行任何操作来显式地对流进行排序。它的作用是消除了流必须保持有序的约束,从而允许后续操作使用不必考虑排序的优化。

样例:

public static void main(String[] args) {
	 Stream.of(5, 1, 2, 6, 3, 7, 4).unordered().forEach(System.out::println);
     Stream.of(5, 1, 2, 6, 3, 7,4).unordered().parallel().forEach(System.out::println);
}
 
 
//两次输出结果对比(方便比较,写在一起)
第一遍:          第二遍:
//第一行代码输出   //第一行代码输出
5                 5
1                 1
2                 2
6                 6
3                 3
7                 7
4                 4
 
//第二行代码输出   //第二行代码输出
3                 3
6                 6
4                 7
7                 5
2                 4
1                 1
5                 2

以上结果,可以看到,虽然用了unordered(),但是第一个循环里的数据顺序并没有被打乱,是不是很好奇?

您可以在Java 8文档中有一下一段内容:

对于顺序流,顺序的存在与否不会影响性能,只影响确定性。如果流是顺序的,则在相同的源上重复执行相同的流管道将产生相同的结果;

如果是非顺序流,重复执行可能会产生不同的结果。 对于并行流,放宽排序约束有时可以实现更高效的执行。

在流有序时, 但用户不特别关心该顺序的情况下,使用 unordered 明确地对流进行去除有序约束可以改善某些有状态或终端操作的并行性能。

3、终结操作

3.1、短路操作

① anyMatch
  • anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足条件则返回 true,否则返回 false。

样例:

Stream<Integer> stream = Stream.of(3, 1, 7, 12);
System.out.println(stream.anyMatch(s -> s > 1));// true
② allMatch
  • allMatch:接收一个 Predicate 函数,当流中每个元素都符合条件时才返回 true,否则返回 false。

样例:

Stream<Integer> stream = Stream.of(3, 1, 7, 12);
System.out.println(stream.allMatch(s -> s > 10));// false
③ noneMatch
  • noneMatch:方法在没有元素匹配时返回true。

样例:

Stream<Integer> stream = Stream.of(3, 1, 7, 12);
System.out.println(stream.noneMatch(s -> s > 10));// false
④ findFirst
  • findFirst:返回流中第一个元素。

样例:

Stream<Integer> stream = Stream.of(3, 1, 12, 16, 9);
System.out.println(stream.findFirst().get());// 3

// filter处理
// System.out.println(stream.filter(s -> s > 3).findFirst().get());// 12
⑤ findAny
  • findAny方法可以在集合中只要找到任何一个所匹配的元素,就返回,此方法在对流并行执行时十分有效(任何片段中发现第一个匹配元素都会结束计算,串行流中和findFirst返回一样。

样例:

List<String> strAry = Arrays.asList("Jhonny", "David", "Jack", "Duke", "Jill", "Dany", "Julia", "Jenish", "Divya");

String result = strAry.parallelStream().filter(s -> s.startsWith("J")).findAny().get();// Jill
String result2 = strAry.stream().filter(s -> s.startsWith("J")).findAny().get();// Jhonny

3.2、非短路操作

① forEach
  • forEach:遍历流中的元素。

样例:

List<String> strAry = Arrays.asList( "Jhonny", "David", "Jack", "Duke", "Jill","Dany","Julia","Jenish","Divya");

strAry.stream().forEach(s-> {
    if("Jack".equalsIgnoreCase(s)) System.out.println(s);// Jack
});
② forEachOrdered
  • forEachOrdered:该方法接收一个 Lambda 表达式,然后按顺序在 Stream 的每一个元素上执行该表达式,forEachOrdered是可以保证循环时元素是按原来的顺序逐个循环的。

样例:

List<String> strAry = Arrays.asList( "Jhonny", "David", "Jack", "Duke", "Jill","Dany","Julia","Jenish","Divya");

strAry.stream().forEachOrdered(s-> {
    if("Jack".equalsIgnoreCase(s)) System.out.println(s);// Jack
});
③ toArray
  • toArray:将流中的元素倒入一个数组。
Object [] toArray();
 
<A> A[] toArray(IntFunction<A[]> generator);

样例:

List<String> strList = Arrays.asList("Jhonny", "David", "Jack", "Duke", "Jill", "Dany", "Julia", "Jenish", "Divya");

Object[] strAryNoArg = strList.stream().toArray();
String[] strAry = strList.stream().toArray(String[]::new);
④ reduce
  • reduce:方法是将流中的元素进行进一步计算的方法。

样例

List<Integer> hearList = Lists.newArrayList();
hearList.add(15);
hearList.add(32);
hearList.add(5);
hearList.add(232);
hearList.add(56);
hearList.add(29);
hearList.add(104);
// 求和
Integer sum = hearList.stream().reduce((x, y) -> x + y).get();
System.out.println("sum:" + sum);// 473
// 简化一下,求和
sum = hearList.stream().reduce(Integer::sum).get();
System.out.println("sum:" + sum);// 473
// 含有初始标识的,求和
sum = hearList.stream().reduce(0, (x, y) -> x + y);
System.out.println("sum:" + sum);// 473
// 对元素的长度进行求和((total,y)->total+y.toString().length(),类似于一个累加器,会被重复调用)
sum = hearList.stream().reduce(0, (total, y) -> total + y.toString().length(),
    (totall, total2) -> totall + total2);
System.out.println("sum:" + sum);// 15
// 简化一下,付元素长度进行求和
sum = hearList.stream().map(Objects::toString).mapToInt(String::length).sum();
System.out.println("sum:" + sum);// 15
⑤ collect
  • collect:称为收集器,是一个终端操作,它接收的参数是将流中的元素累积到汇总结果的各种方式。

第一种方式会比较经常使用到,也比较方便使用,现在先看一看里面常用的一些方法:

工厂方法返回类型用于
toListList把流中所有元素收集到List中
示例:List<Menu> menus=Menu.getMenus.stream().collect(Collectors.toList());
工厂方法返回类型用于
toSetSet把流中所有元素收集到Set中,删除重复项
示例:Set<Menu> menus=Menu.getMenus.stream().collect(Collectors.toSet());
工厂方法返回类型用于
toCollectionCollection把流中所有元素收集到给定的供应源创建的集合中
示例:ArrayList<Menu> menus=Menu.getMenus.stream().collect(Collectors.toCollection(ArrayList::new));
工厂方法返回类型用于
CountingLong计算流中元素个数
示例:Long count=Menu.getMenus.stream().collect(counting);
工厂方法返回类型用于
SummingIntInteger对流中元素的一个整数属性求和
示例:Integer count=Menu.getMenus.stream().collect(summingInt(Menu::getCalories));
工厂方法返回类型用于
averagingIntDouble计算流中元素integer属性的平均值
示例:Double averaging=Menu.getMenus.stream().collect(averagingInt(Menu::getCalories));
工厂方法返回类型用于
JoiningString连接流中每个元素的toString方法生成的字符串
示例:String name=Menu.getMenus.stream().map(Menu::getName).collect(joining(,));
工厂方法返回类型用于
maxByOptional一个包裹了流中按照给定比较器选出的最大元素的optional,如果为空返回的是Optional.empty()
示例:Optional<Menu> fattest=Menu.getMenus.stream().collect(maxBy(Menu::getCalories));
工厂方法返回类型用于
minByOptional一个包裹了流中按照给定比较器选出的最小元素的optional,如果为空返回的是Optional.empty()
示例: Optional<Menu> lessest=Menu.getMenus.stream().collect(minBy(Menu::getCalories));
工厂方法返回类型用于
Reducing归约操作产生的类型从一个作为累加器的初始值开始,利用binaryOperator与流中的元素逐个结合,从而将流归约为单个值
示例:int count=Menu.getMenus.stream().collect(reducing(0,Menu::getCalories,Integer::sum));
工厂方法返回类型用于
collectingAndThen转换函数返回的类型包裹另一个转换器,对其结果应用转换函数
示例:Int count=Menu.getMenus.stream().collect(collectingAndThen(toList(),List::size));
工厂方法返回类型用于
groupingByMap<K,List>根据流中元素的某个值对流中的元素进行分组,并将属性值做为结果map的键
示例:Map<Type,List<Menu>> menuType=Menu.getMenus.stream().collect(groupingby(Menu::getType));
工厂方法返回类型用于
partitioningByMap<Boolean,List>根据流中每个元素应用谓语的结果来对项目进行分区
示例:Map<Boolean,List<Menu>> menuType=Menu.getMenus.stream().collect(partitioningBy(Menu::isType)

第二种方式看起来跟reduce的三个入参的方法有点类似,也可以用来实现filter、map等操作!

流程解析图如下:

在这里插入图片描述

⑥ max
  • max:返回此流的最大元素

样例:

List<Integer> num = Arrays.asList( 4, 5, 6);
num.stream().max(Integer::compareTo).ifPresent(System.out::println);// 6
⑦ min
  • min:返回此流的最小元素

样例:

List<Integer> num = Arrays.asList( 4, 5, 6);
num.stream().min(Integer::compareTo).ifPresent(System.out::println);// 4
⑧ count
  • count:返回此流中的元素计数

样例:

List<Integer> num = Arrays.asList( 4, 5, 6);
System.out.println(num.stream().count());// 3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值