别再一味地用for循环了,上Stream(流)起飞

理论

1、什么是Stream(流)

Stream,中文叫流,Java8(JDK1.8版本)的新特性,官方说明是这样的:

A sequence of elements supporting sequential and parallel aggregate operations.

什么意思?翻译成中文:

sequence:序列,也可以理解为队列。
sequential:按次序的,顺序的。
parallel:并行的。
aggregate:聚合。

合起来就是:

一种支持顺序和并行聚合操作的元素序列(或者队列)。

通俗一点:

它是一个排好队的队列(是的,排好队的,流在创建前元素是什么顺序的,创建后就是什么顺序的),队列中是相同类型的元素(多少个元素随意,0 ~ ∞),它既可以一个一个按顺序聚合操作元素,也可以一次性操作多个元素。

2、Stream的特点

  • No storage
  • Functional in nature
  • Laziness-seeking
  • Possibly unbounded
  • Consumable

一个一个来:

No storage
无存储。
流虽然是一种数据结构,但它是不存储元素的,它只传输元素,即用即释。

Functional in nature
功能性。
所有对流的操作只会产生一个结果,而不会修改源数据。比如过滤功能,它不是把要过滤的元素从源数据中删除,而是产生一个新的流,这个新的流包含的是过滤以后的元素。

Laziness-seeking
懒散寻找。
这四个字怎么理解?就比如说我要找一下有没有一个叫张三的人,我只要找到一个就行,我没必要找出所有叫张三的人,有可能他就排在第一个,那后面我就懒得找了。
懒散寻找只针对流的中间操作的,到终端操作它就会变得很勤奋,后面会讲到什么是中间操作和终端操作。

Possibly unbounded
可以无限装载。
流是可以无限的,不像集合的大小是有限的。但是它只是说流可以无限装载,而不能无限操作。
……
一个无限的流你想等到猴年马月把它操作完?所以这里有个短路操作,比如截取一部分进行操作,或者直接取第一个这种。

Consumable
消耗品。
这个在【注意点2】里面说明。

注意点1:

流操作完后是要用一个结果去接的,哪怕是用forEach也是在产生一个结果,不然你干巴巴地去产生这个流然后去操作它干啥?

注意点2:

流是一次性消耗品,一个流只能操作一次,产生结果以后流就消失了,再操作就会报错。用一段代码解释一下:

Stream<Integer> stream = Stream.of(1, 2, 3);
long count = stream.count();
long cut = stream.limit(2).count();

上面这段代码在编译时是正常通过的,只有在运行时才会报错,报错代码如下:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
	at java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94)
	at java.util.stream.ReferencePipeline$StatefulOp.<init>(ReferencePipeline.java:647)
	at java.util.stream.SliceOps$1.<init>(SliceOps.java:120)
	at java.util.stream.SliceOps.makeRef(SliceOps.java:120)
	at java.util.stream.ReferencePipeline.limit(ReferencePipeline.java:401)

很明显,流已经关闭消失了,就像一包薯条已经被你吃下肚了,还想吃?花钱再去买一包新的。

3、管道说法的由来

这么理解,一个完整的流操作是这样的:

创建流 > 中间操作 > 终端操作

而中间操作是可以有N个的,因此,流程可以是这样:

创建流 > 中间操作 > 中间操作 > …… > 中间操作 > 中间操作 > 终端操作

看上去就像一节一节的管子连接在一起,形成一根管道。
什么是管道?水管见过吧?水管也没见过?算了,拖出去吧。

这里解释一下两个词语:

Intermediate
中间操作。
首先流需要创建出来,然后根据需求进行一系列的过滤、转换、映射、加减乘除等操作,这些操作不会直接输出结果,而是把原始的那个流变成各种各样的流,它还是一个流,仍在等待输出。
这些执行的操作就叫中间操作。

Terminal
终端操作。
终端操作就是把处理完的流输出成一个结果,可以是boolean、int、long、Optional、数组、集合等类型,根据调用的方法来决定。
前面说中间操作是懒散的,并不一定会遍历所有元素,而终端操作是勤奋的,必须遍历所有元素,不然输出的结果就是有问题的。

4、为什么说不要一味地用for循环

在以前,我们用迭代器或for循环来处理集合或数组等,简单的逻辑可以一笔带过,但是遇到复杂的逻辑,可能会用到多个循环,大段大段的代码,看得头疼,或者有些同学来个更牛逼的,for循环里套for循环,那不是瞎了?而且迭代器和for循环都是串行的,处理大批量的复杂数据时效率就低下了。
所以出了Stream,不仅可以一笔带过,提高效率,逻辑一目了然,显得代码看上去很牛逼,还能并行处理元素,效率高多了,要是迭代器和for循环的话还得自己创建线程或引入线程池,麻烦。
好吧,主要为了看上去牛逼。

5、不干扰和无状态行为

官方建议是不要去干扰流和不要对流做出有状态行为。
这两个都好理解:

不干扰流,在流的执行过程当中不要去修改源数据,起码在串行流下不要去做这种事,这样很可能会得到错误的结果(比如本来要进行累加的,然后其中一个元素先被删掉了),也有可能会报异常(比如加了一个不同类型的元素进去)。
除非这是个并发流,或者你知道你要干什么,如果你不是很清楚你要干什么,还是不要动它比较好。

无状态行为,流在执行过程中对元素都是一次性操作,操作过就过去了,不会再重复使用,就像我们上面所说的一次性消耗品,这样能够保证的是每次执行这种流输出的结果都是一样的。
有状态行为就是说存储了中间过程,比如流的sorted(排序)和distinct(去重)操作,它会记住前面每一次操作后的历史结果,直到输出为止。一般来讲,串行流是不用担心结果问题的,主要是并行流,这种涉及到线程不安全情况,导致结果变得乱七八糟或者报错,需要你自己上好锁或者加上同步状态。

实践

下面遍历一下Stream中所有的方法。

1、builder / of

builder
Returns a builder for a Stream
返回一个流生成器。
返回类型:static <T> Stream.Builder<T>

of
Returns a sequential ordered stream whose elements are the specified values
根据给定值返回一个顺序流,给的值是什么顺序就是什么顺序。
返回类型:static <T> Stream<T>

Stream.Builder<Integer> builder = Stream.builder();
builder.add(1);
builder.add(2);
builder.add(3);
Stream<Integer> stream = builder.build();
// 或者
Stream.Builder<Integer> builder = Stream.builder();
builder.accept(1);
builder.accept(2);
builder.accept(3);
Stream<Integer> stream = builder.build();
// add源码中会先执行accept,所以直接用accept就行

或者可以这么创建:

Stream<Integer> stream1 = Stream.of(1, 2, 3);
// 下面两种创建出来的流元素是集合对象,要获取其中的值需要自己遍历或者把流铺平
Stream<List<Integer>> stream2 = Stream.of(Arrays.asList(1, 2, 3));
Stream<List<String>> stream3 = Stream.of(
     Arrays.asList("1", "2", "3"),
     Arrays.asList("4", "5", "6")
);

一般情况我们是这么创建的:

List<DemoBO> list = xxx.xxx(); // xxx是自己调用的业务方法,大家都看得懂
list.stream().xxx……; // list.stream()就是创建成流了,后面的xxx就是自己根据业务需求调用的方法

2、count

Returns the count of elements in this stream
返回该流中元素的个数。
没啥必要,一般都是先创建出集合或者数组,那直接获取长度就行了,没必要再转成流然后获取长度。而且这个方法是有隐患的,当流是空的时候会报空指针异常。
啥时候用呢?流经过一系列处理以后,你已经不知道它多长了,输出一下看看。
返回类型:long

Stream<Integer> stream = Stream.of(1, 2, 3);
long count = stream.count();

3、empty

Returns an empty sequential Stream
返回一个空的顺序流。
这个方法有点莫名其妙吼。
为啥要放出来?因为莫名其妙吼。
返回类型:static <T> Stream<T>

Stream<String> stream = Stream.empty();

4、xxxMatch

match相关的有三个:

allMatch
Returns whether all elements of this stream match the provided predicate
当流中的所有元素都符合指定条件才返回true,不会判断流中的所有结果,只要有一个不符合,直接跳出,不会执行剩下的。
返回类型:boolean

Stream<Integer> stream = Stream.of(1, 2, 3);
boolean flag = stream.allMatch(item -> item > 1); // 结果是false

anyMatch
Returns whether any elements of this stream match the provided predicate
只要流中有一个元素符合指定条件就返回true,不会判断流中的所有结果,只要有一个符合,直接跳出,不会执行剩下的。
返回类型:boolean

Stream<Integer> stream = Stream.of(1, 2, 3);
boolean flag = stream.anyMatch(item -> item > 1); // 结果是true

noneMatch
Returns whether no elements of this stream match the provided predicate
当流中所有的元素都不符合指定条件才返回true,不会判断流中的所有结果,只要有一个符合,直接跳出,不会执行剩下的。
返回类型:boolean

Stream<Integer> stream = Stream.of(1, 2, 3);
boolean flag = stream.noneMatch(item -> item > 1); // 结果是false

5、flatMap / flatMapToDouble / flatMapToInt / flatMapToLong

flatMap
Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element
把一个流转换成另一个流,加上各种条件或修饰,或者原来的流是一对多的,把它平铺开来。
返回类型:<R> Stream<R>(依旧是个流)

拿上面创建的集合流做演示:

Stream<List<String>> stream1 = Stream.of(
     Arrays.asList("1", "2", "3"),
     Arrays.asList("4", "5", "6")
);
Stream<String> stream2 = stream1.flatMap(Collection::stream); // List被平铺成String了

flatMapToDouble / flatMapToInt / flatMapToLong
Returns a/an DoubleStream/IntStream/LongStream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element
意思跟flatMap一样,只是返回类型不一样。
返回类型:DoubleStream / IntStream / LongStream(依旧是个流)

Stream<String> stream1 = Stream.of("1", "2", "3");
DoubleStream stream2 = stream1.flatMapToDouble(item -> DoubleStream.of(Double.parseDouble(item)));
IntStream stream3 = stream1.flatMapToInt(item -> IntStream.of(Integer.parseInt(item)));
LongStream stream4 = stream1.flatMapToLong(item -> LongStream.of(Long.parseLong(item)));

6、sorted

Returns a stream consisting of the elements of this stream, sorted according to natural order
对该流进行排序。
返回类型:Stream<T>(依旧是个流)

当你不自定义规则的时候,系统会帮你从小到大排序:

Stream<Integer> stream = Stream.of(4, 5, 6, 1, 2, 3);
stream.sorted().xxx……; // 会排序成1, 2, 3, 4, 5, 6

或者你可以自定义规则:

Stream<Integer> stream = Stream.of(4, 5, 6, 1, 2, 3);
stream.sorted((a, b) -> b - a).xxx……; // 会排序成6, 5, 4, 3, 2, 1

7、distinct

Returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this stream
就是去重,很好理解。
返回类型:Stream<T>(依旧是个流)

Stream<Integer> stream = Stream.of(1, 1, 1);
stream.distinct().xxx……;

8、filter

Returns a stream consisting of the elements of this stream that match the given predicate
这是一个很常用也是很好用的方法,现实业务逻辑中我们会用到大量的for循环去过滤和筛选我们的数据,用这个filter来代替大段大段的代码,你会发现一下子轻松好多,参数中可以加一个或N个判断条件。
返回类型:Stream<T>(依旧是个流)

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
stream.filter(item -> item > 1 && item < 5).xxx……;

9、forEach / forEachOrdered

forEach
Performs an action for each element of this stream
对该流的每个元素执行一次操作。
返回类型:void

Stream<Integer> stream = Stream.of(1, 2, 3);
stream.forEach(System.out::println); // 控制台依次打印出1, 2, 3

forEachOrdered
Performs an action for each element of this stream, in the encounter order of the stream if the stream has a defined encounter order
对该流的每个元素执行一次操作,如果该流有一个自定义的顺序,会按照该顺序执行。
这个自定义顺序的意思不是让你去括号里面自己写条件,而是说它会按照传过来的流的顺序依次执行,也就是说用这个方法不用担心在执行时它会把顺序打乱,哪怕你用并行流也没关系。
返回类型:void

Stream<Integer> stream = Stream.of(1, 2, 3);
stream.forEachOrdered(System.out::println); // 控制台打印出来就是1, 2, 3
// 哪怕我用并行流
stream.parallel().forEachOrdered(System.out::println); // 控制台打印出来依旧是1, 2, 3,而不是奇怪的2, 3, 1或者3, 1, 2

10、findAny / findFirst

findAny
Returns an Optional describing some element of the stream, or an empty Optional if the stream is empty
返回该流的任意元素(随它心情返回),如果流是空的,返回空,不会报空指针异常。
返回类型:Optional<T>

Stream<Integer> stream = Stream.of(1, 2, 3);
Optional<Integer> num = stream.findAny();

findFirst
Returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty
返回该流的第一个元素,如果流是空的,返回空,不会报空指针异常。
返回类型:Optional<T>

Stream<Integer> stream = Stream.of(1, 2, 3);
Optional<Integer> num = stream.findFirst();

11、limit / skip

limit
Returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length
截取一段流。从哪里开始截取?从头开始。截多长?自己指定一个长度,这个长度别超过流的长度,虽然说超过了一样能执行,但是不觉得这样写代码很Low吗?除非你创建的是一个无限流,那随你截。
返回类型:Stream<T>(依旧是个流)

Stream<Integer> stream = Stream.of(1, 2, 3);
stream.limit(2).xxx……;

skip
Returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream
limit相反,它是扔掉前面一个或N个元素,然后返回剩下的部分。
返回类型:Stream<T>(依旧是个流)

Stream<Integer> stream = Stream.of(1, 2, 3);
stream.skip(2).xxx……;

12、max / min

max
Returns the maximum element of this stream according to the provided Comparator
根据指定条件返回该流中的最大值。
返回类型:Optional<T>

Stream<Integer> stream = Stream.of(1, 2, 3);
Optional<Integer> num = stream.max(Integer::compareTo);

min
Returns the minimum element of this stream according to the provided Comparator
根据指定条件返回该流中的最小值。
返回类型:Optional<T>

Stream<Integer> stream = Stream.of(1, 2, 3);
Optional<Integer> num = stream.min(Integer::compareTo);

13、collect / toArray

collect
Performs a mutable reduction operation on the elements of this stream
对该流的元素执行一个可变缩减操作。其实就是把流再变回成我们想要的集合。
返回类型:<R,A> R 或 <R> R

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list = list.stream().xxx…….collect(Collectors.toList());
// 还有一种情况是这样的
list = list.parallelStream().xxx…….collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

上面第二种情况是针对并行流的,串行流情况下第三个参数是不会执行的,解释同后面的reduce

toArray
Returns an array containing the elements of this stream, using the provided generator function to allocate the returned array, as well as any additional arrays that might be required for a partitioned execution or for resizing
返回一个包含该流元素的数组,参数可以自定义函数,甚至可以指定数组大小。
返回类型:Object[] 或者 <A> A[]

Stream<Integer> stream = Stream.of(1, 2, 3);
Integer[] array = stream.toArray(Integer[]::new);

14、concat

Creates a lazily concatenated stream whose elements are all the elements of the first stream followed by all the elements of the second stream
把两个流拼接在一起,两个流的类型必须是一样的,拼在一起后顺序不会被打乱。如果有一个流是并行流,那么拼接后的流也是并行流。
返回类型:static <T> Stream<T>(依旧是个流)

Stream<String> stream1 = Stream.of("1", "2", "3");
Stream<String> stream2 = Stream.of("4", "5", "6");
Stream<String> stream3 = Stream.concat(stream1, stream2);

15、generate / iterate

generate
Returns an infinite sequential unordered stream where each element is generated by the provided Supplier
返回一个无限流,参数自定义,该流是无序的。
因为流是无限的,要操作的话必须自己截取一段。
返回类型:static <T> Stream

Stream<Integer> stream = Stream.generate(new Random()::nextInt);
stream.limit(10).xxx……;

iterate
Returns an infinite sequential ordered Stream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc
也是返回一个无限流,参数自定义,但这个流是排好序的,而且要指定一个种子值,什么是种子值?就是种子选手。其实就是初始值。
返回类型:static <T> Stream

Stream<Integer> stream = Stream.iterate(1, item -> item + 1);
stream.limit(10).xxx……;

16、map / mapToDouble / mapToInt / mapToLong

map
Returns a stream consisting of the results of applying the given function to the elements of this stream
flatMap类似,也是把一个流转换成另一个流,指定自定义的函数方法。唯一不同的是flatMap可以一对多,而map只能一对一。
返回类型:<R> Stream<R>(依旧是个流)

Stream<String> stream1 = Stream.of("1", "2", "3");
Stream<Integer> stream2 = stream1.map(Integer::parseInt);

mapToDouble / mapToInt / mapToLong
同上,不重复说了。
返回类型:DoubleStream / IntStream / LongStream(依旧是个流)

Stream<String> stream1 = Stream.of("1", "2", "3");
IntStream stream2 = stream1.mapToInt(Integer::parseInt);
DoubleStream stream3 = stream1.mapToDouble(Double::parseDouble);
LongStream stream2 = stream1.mapToLong(Long::parseLong);

17、peek

Returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream
前面说过流是一次性消费品,用过就没了,但是这个peek有点牛逼,它能让你不断重复地消耗同一个流,直到你执行终端操作为止。但它仅仅是消耗,不会有任何值返回,不会去改变(破坏)原来的流,相当于是创建了一个临时流来给你用,所以它的参数方法返回类型是void
返回类型:Stream<T>(依旧是个流)

Stream<Integer> stream = Stream.of(1, 2, 3);
stream.peek(System.out::println)
	.filter(item -> item > 1)
	.peek(System.out::println)
	.map(item -> item + 1)
	.forEach(System.out::println);

注意点:

官方给了这么一句话:
This method exists mainly to support debugging
也就是说peek主要是用来调试(Debug)的,既然这样,你看看就行了,别费尽心思用它去改数据啊啥的,想peach呢。

18、reduce

Performs a reduction on the elements of this stream, using the provided identity, accumulation and combining functions
熟悉MapReduce编程模型的人都知道,这是一个把任务分解再整合的过程,流中的reduce各方面意义都相同,也是整合。
整合整合,多个整合成一个,最终返回的就是一个整合后的结果,是一个。
返回类型:Optional<T> 或 T 或 <U> U

Stream<Integer> stream = Stream.of(1, 2, 3);
// 不指定起始值,返回类型就是Optional<T>
Optional<Integer> sum1 = stream.reduce(Integer::sum); // 结果是6
// 指定起始值,返回类型就是T,跟指定的起始值同类型
Integer sum2 = stream.reduce(1, Integer::sum); // 结果是7
// 稍微讲下第三种情况,返回类型根据第三个参数来定
Integer sum3 = stream.parallel().reduce(1, Integer::sum, Integer::sum); // 结果是9

上面的第三种情况,前两个参数跟第二个情况一样,指定初始值和执行整合方法,主要是第三个参数,加了这个参数以后最终的整合在这里执行,但是仅限于并行流情况下,如果是串行流,用第二种情况就行,因为第三个参数它不会去执行。
通过结果可以看到,同样是初始值是1,再加上流中的三个值,第二种情况结果是7,而第三种情况结果却是9,原因是并行情况下第三个参数把每一次第二个参数执行的结果都累加起来了,也就是 1 + 1 加上 1 + 2 加上 1 + 3,最终等于9。
所以说并行情况下要做好控制,不然会出现重复计算。

归类

1、中间操作

  • filter
  • map
  • mapToInt
  • mapToLong
  • mapToDouble
  • flatMap
  • flatMapToInt
  • flatMapToLong
  • flatMapToDouble
  • distinct
  • sorted
  • peek
  • limit
  • skip

2、终端操作

  • forEach
  • forEachOrdered
  • toArray
  • reduce
  • collect
  • min
  • max
  • count

3、短路操作

  • anyMatch
  • allMatch
  • noneMatch
  • findFirst
  • findAny

4、创建

  • builder
  • empty
  • of
  • iterate
  • generate
  • concat

猜你喜欢

转载自blog.csdn.net/weixin_48140105/article/details/121901117