Stream 流
根据官方的说法是:流支持一系列元素的串行或者并行等聚合操作。
流由三部分构成:1.源、2.零个或者多个中间操作、3.终止操作
流操作的分类:1.惰性操作、2.及早求值;(对于流来说可以进行一系列链式操作,然而这系列中间链式操作,也就是惰性操作,只有遇到了终止操作也就是及早求值。)
流实例的创建方式
- 1.使用Stream中的静态方法
Stream<String> stream = Stream.of("hello","world");//of方法接收一个可变参数
- 使用数组方式创建
String[] str = new String[]{"hello","world"};
Stream<String> stream1 = Arrays.stream(str);
其实上面的方式底层就是一样的,我们可以看一下of方法的源代码:
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
- 通过集合的方式创建
List<String> list = Arrays.asList(str);
Stream<String> stream2 = list.stream();
该方式也是最常见的方式
关于流相关的例子
关于流相关的方法,采用例子的方式来了解流。
- 1.打印3-7的序列,不包括7
public class StreamTest {
public static void main(String[] args) {
IntStream.range(3,7).forEach(System.out::println);
}
}
- 2.打印3-7的序列,包括7
public class StreamTest {
public static void main(String[] args) {
IntStream.rangeClosed(3,7).forEach(System.out::println);
}
}
上面的两个例子主要讲解列IntStream的另一个实例方式
- 3.将一个整形数组的数据进行乘以2再输出
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4);
list.stream().map(item->item*2).forEach(System.out::println);
//可以理解为将集合转换为流对象然后对流中的数据进行一个映射->映射后的值,所有就用map方法
}
}
- 4.将一个整形数组的数据都乘以2,并求出其总和。
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4);
Integer result = list.stream().map(item->item*2).reduce(0,Integer::sum);
System.out.print(result);
}
}
reduce()
方法在Stream中有三个重载方法。reduce可以叫做聚合操作。就是对流中的每个元素执行某个操作,让其输出一个聚合后的数据。当然它也是终止操作。
将stream对象转换为一个数组
方式一:使用stream
的toArray()
方法。该方法默认转换回来的数组是Object类型的数组。
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
Object[] str = stream.toArray();
Arrays.asList(str).forEach(System.out::println);
}
}
方式二:使用Stream
的toArray(IntFunction<A[]> generator)
,该方法可以用户定义产生数组的长度。这里给出该方法的说明generator a function which produces a new array of the desired type and the provided length
;
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
//String[] strArra = stream.toArray(String[]::new);
String[] strArra = stream.toArray(length->new String[length]);
Arrays.asList(strArra).forEach(System.out::println);
}
}
将流转换为一个List集合
关于将流转换为一个List集合对象,可以使用collect
方法:
/**
* Performs a <a href="package-summary.html#MutableReduction">mutable
* reduction</a> operation on the elements of this stream. A mutable
* reduction is one in which the reduced value is a mutable result container,
* such as an {@code ArrayList}, and elements are incorporated by updating
* the state of the result rather than by replacing the result. This
* produces a result equivalent to:
* <pre>{@code
* R result = supplier.get();
* for (T element : this stream)
* accumulator.accept(result, element);
* return result;
* }</pre>
* @param <R> type of the result
* @param supplier a function that creates a new result container. For a
* parallel execution, this function may be called
* multiple times and must return a fresh value each time.
* @param accumulator an <a href="package-summary.html#Associativity">associative</a>,
* <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function for incorporating an additional element into a result
* @param combiner an <a href="package-summary.html#Associativity">associative</a>,
* <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function for combining two values, which must be
* compatible with the accumulator function
*/
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
对于该方法的理解:第一个参数:转换后的集合类型(也就是说是一个什么类型的集合),第二个参数:就是对该集合执行的哪些操作。第三个参数:将两个操作结起来
下面举一个例子:
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
//List<String> list1 = stream.collect(ArrayList::new,ArrayList::add,ArrayList::addAll);
List<String> list1 = stream.collect(()->new ArrayList(),(list,item)->list.add(item),(list,list2)->list.addAll(list2));
list1.forEach(System.out::println);
}
}
总结:supplier:提供容器,accumulator:向容器中添加数据,combiner:把多个容器实例拼接成一个有完整数据的容器。
上面的方式是jdk8的一种原始方式,所以比较复杂。所以,因此jdk8也提供了一种更方便的方式。就是collect
方法接收一个Collertors
类型的参数。该参数类型是一个接口,接口中有许多相关的方法。
首先:给出该方式的例子:
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
List<String> list = stream.collect(Collectors.toList());
list.forEach(System.out::println);
}
}
我们查看关于toList()
方法源码就可以发现,其实他就是对原始的方式进行了一次封装:
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
但是通过这种方式,可以发现,转换后的集合就只能是ArrayList类型的集合。那么如果我不想使用ArrayList类型的集合怎么办呢?
答案是:可以使用Collertors
封装好的另一个方法toCollection
方法。
public static <T, C extends Collection<T>>
Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
return new CollectorImpl<>(collectionFactory, Collection<T>::add,
(r1, r2) -> { r1.addAll(r2); return r1; },
CH_ID);
}
collectionFactory:就是用户自己决定使用什么容器来接收流中的元素。
例子:
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
List<String> list = stream.collect(Collectors.toCollection(LinkedList::new));
list.forEach(System.out::println);
}
}
其实Collectors中除了可以指定把流转换为对应的集合,他还可以把流转换为字符串
- 第一种:没有分割符
public static Collector<CharSequence, ?, String> joining() {
return new CollectorImpl<CharSequence, StringBuilder, String>(
StringBuilder::new, StringBuilder::append,
(r1, r2) -> { r1.append(r2); return r1; },
StringBuilder::toString, CH_NOID);
}
例子:
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
String result = stream.collect(Collectors.joining());
System.out.print(result);
}
}
- 第二种:有分隔符
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {
return joining(delimiter, "", "");
}
例子:
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
String result = stream.collect(Collectors.joining(";"));
System.out.print(result);
}
}
- 第三种:有分隔符,同时也有前缀和后缀
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
return new CollectorImpl<>(
() -> new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}
例子:
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
String result = stream.collect(Collectors.joining(";","&","$"));
System.out.print(result);
}
}
流实例剖析
- 1.将一个字符流中的每个元素转为大写输出
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
stream.map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);
}
}
这里使用的map方法属于中间操作,map的作用上面也介绍了,其实他就是把流中的元素映射为其他类型的数据,然后再返回为一个新的流。(注意:流所有的中间操作都会返回一个全新的流对象)。所以:map一般是在我们需要对流中的数据进行转换的时候就会使用到。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//可以看到map方法接收一个Function类型的参数,也就是说用户自定义一个操作,把流中的值
//映射为其他值。Function的输出类型就是流中每个元素的类型。
- 2.把一个类似于List<List>集合中的每个元素乘以2输出
public class StreamTest {
public static void main(String[] args) {
List<List<Integer>> list = Arrays.asList(Arrays.asList(1),Arrays.asList(2,3),Arrays.asList(4,5));
list.stream().flatMap(item->item.stream()).map(item->item*2).collect(Collectors.toList()).forEach(System.out::println);
}
}
这里的flatMap方法也同样属于中间操作:flatMap会把流对象中的每个元素转换为流,然后把每个元素的流再映射成一个包含所有元素的流对象。(也就是把List集合中的每个List类型的元素转换为流。最后把每个元素的流合并成一个新的流,因此新的流中就有List集合中的所有元素。–该方法也有一个中文名称叫做打平,意思就是将集合中的List类型的元素打平)。因此,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> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
关于flatMap的方法再来一个例子:把一个String流中的每个元素中的单词进行去重打印出来
public class StreamTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello welcome","hello world","world hello","hello hello welcome","welcome hello");
list.stream().map(item->item.split(" ")).flatMap(Arrays::stream).distinct().forEach(System.out::println);
}
}
//输出结果:
hello
welcome
world
- 3.通过流的方式生成1 2 4 6 … 256类型的等差数列。
public class StreamTest {
public static void main(String[] args) {
Stream.iterate(1,item->item*2).limit(9).forEach(System.out::println);
}
}
- 4.在3的基础上找出流中大于2的元素,然后将每个元素乘以2,然后忽略流中的前两个元素,最后取出流中前两个元素的总和。
public class StreamTest {
public static void main(String[] args) {
int result = Stream.iterate(1,item->item*2).limit(9).filter(item->item>2).mapToInt(item->item*2).skip(2).limit(2).sum();
System.out.println(result);
}
}
流相关的陷阱
首先我们通过一段程序运行的结果来看:
public class StreamTest {
public static void main(String[] args) {
Stream<Integer> stream = Stream.iterate(1,item->item*2).limit(6);
System.out.println(stream);
System.out.println(stream.map(item->item + 2));
}
}
//结果:
//java.util.stream.SliceOps$1@6d311334
//java.util.stream.ReferencePipeline$3@214c265e
通过这段程序我们可以发现,流(Stream) 每次执行一次操作后就会生成一个新的流对象。那么原先的流对象就会关闭,最后被Java虚拟机垃圾回收掉。下面的一段程序就可以证明已经使用的流就不能再使用啦,因为使用过后的流对象就关闭啦!就不能再被使用了:
public class StreamTest {
public static void main(String[] args) {
Stream<Integer> stream = Stream.iterate(1,item->item*2).limit(6);
System.out.println(stream.filter(item -> item>2));
System.out.println(stream.map(item->item + 2));
}
}
//下面给出报错信息:
//Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
接下来再看一段程序:
public class StreamTest {
public static void main(String[] args) {
Stream<Integer> stream = Stream.iterate(1,item->item*2).limit(6);
stream.mapToInt(item -> {
item = item +2;
System.out.println(item);
return item;
});
}
}
运行这段程序控制台并不会输出任何东西。原因是,map操作属于中间操作,流的所有中间操作都是属于惰性的。
接下来再看一段程序:
public class StreamTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello","world","hello world");
list.stream().mapToInt(item -> {
int length = item.length();
System.out.println(item);
return length;
}).filter(length -> length >= 5).findFirst().ifPresent(System.out::println);
}
}
这段程序,可以猜测一下输出的结果如何,其实输出的结果是:
hello
5
那么问题来了,为什么输出的结果是这样呢?而不是下面这样的呢?
hello
5
world
5
hello world
11
其实关于这个问题是这样的:首先需要纠正一点的是,流并不是把流中的所有元素拿去执行一个中间操作过后,再去执行下一个中间操作的。而是一个个元素一次性的去执行流的所有操作。(下面会说到:流是内部迭代的,而是把流中的每个元素像通过管道一样的方式执行下去的)。最后一点就是:流是存在短路的,当找到自己所需要的结果时,就不会再继续往下走啦!
同过上面的程序就可以证明关于流的几点特征:
- 流在执行每次操作后都会生成一个新的流对象
- 已经被使用过的流对象就不能再次被使用,因为已经使用的流对象已经被关闭啦!
- 流的中间操作都是惰性的。只有遇到终止操作才会去执行中间操作。
- 流是存在短路的。
内部迭代与外部迭代
内部迭代:就是关于所有操作都是在内部进行的。比如:对一个int类型流的所有元素都加1的操作,就是进行内部迭代的。
外部迭代:就是所有操作,都是在外部进行的。比如:使用for循环遍历数组/集合中的每个元素,就是使用的外部迭代。
public void outer(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
for(Integer i : list){
System.out.println(i);
}
}
这段程序就属于外部迭代。就像一个指针一样,首先指针指向第一个元素,每次操作后,指针都向后移一个位置指向下一个元素。
public void inner(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
list.stream().forEach(System.out::println);
}
这段程序就是属于内部迭代的方式。首先将list集合转换为一种流,再通过流依次输出每个元素。这种方式就好像管道一样,数组中的每个元素以流的方式通过管道输出到控制台。这样,我们并没有给list集合外加一个迭代器,让该迭代器去执行list集合中的每个元素。而是将list集合转换为流的形式通过管道依次输出。
通过上面解释什么是内部迭代和外部迭代过后,我们也说了内部迭代就好比管道一样。其实我们完全可以把流的每个中间操作当当做一节管道,将集合或者数组当做源,将终止操作当做管道出口后到达的位置。就以下面的程序来详细的描述下:
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
list.stream().map(item->item * 2).filter(item -> item > 3).skip(1).forEach(System.out::println);
}
}
仔细阅读这段程序:list可以作为流的源,map、filter、skip就相当于三节具有不同功能的管道。源首先通过map管道过后就变成map对源操作后的结果源。然后结果源再通过filter管道,然而filter管道需要过滤小于3的元素,因此小于三的元素就被过滤掉了,就不能进入skip管道。接下来就是进入skip管道,然而skip管道会把第一个进入该管道的元素给过滤掉,其他元素就能顺利的通过skip管道。通过skip管道过后的元素就会离开管道,然后就被输出到控制台。
这一系列的操作都是通过不同的管道过后的结果。所以流是属于内部迭代的。
如果要通过外部迭代的方式执行和上面相同的list集合,并输出相同的结果就是这样的:
public class StreamTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
int temp = 0;
List<Integer> tempList = new ArrayList<>();
for(int i : list){
temp = i * 2;
if(temp > 3){
tempList.add(temp);
}
}
for(int j = 0;j<tempList.size();j++){
if(j == 0){
continue;
}
System.out.println(tempList.get(j));
}
}
}
首先通过这段程序第一反应就是,使用外部迭代的方式让上面只需要一行就能搞定的事情,但是现在就需要如此多操作。 这种外部迭代的方式就是通过Iterator(迭代器)来执行集合中的每个元素的。 显然不用我来进行比较了吧!
该文章也算把我学习jdk8关于流stream的使用 给记录下来啦