java8 Stream常用方法和特性浅析
前言:对大数据量的集合的循环处理,stream拥有极大的优势,完全可以用stream去代替for循环。
Stream介绍
先说下Stream的优势:它是java对集合操作的优化,相较于迭代器,使用Stream的速度非常快,并且它支持并行方式处理集合中的数据,默认情况能充分利用cpu的资源。同时支持函数式编程,代码非常简洁。
Stream是一种用来计算数据的流,它本身并没有存储数据。你可以认为它是对数据源的一个映射或者视图。
它的工作流程是:获取数据源->进行一次或多次逻辑转换操作->进行归约操作形成新的流(最后可以将流转换成集合)。
1.生成流
Stream的创建需要一个数据源(通常是一个容器或者数组):
例1:Stream<String> stream = Stream.of("I", "got", "you", "too");
例2:String [] strArray = new String[] {"a", "b", "c"};
stream = Arrays.stream(strArray);
例3:List<String> list = Arrays.asList(strArray);
stream = list.stream();
2.流的操作
流的操作类型分2种:中间操作与聚合操作。
2.1中间操作(intermediate ):
中间操作就是对容器的处理过程,包括:排序(sorted...),筛选(filter,limit,distinct...),映射(map,flatMap...)等
2.1.1 排序操作(sorted):(参考:https://www.cnblogs.com/a-du/p/8289537.html)
sorted提供了2个接口:
1、sorted() 默认使用自然序排序, 其中的元素必须实现Comparable 接口 。
2、sorted(Comparator<? super T> comparator) :我们可以使用lambada 来创建一个Comparator 实例。可以按照升序或着降序来排序元素。
比如:将一些字符串在地址中按出现的顺序排列:
String address = "中山北路南京大学仙林校区";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大学");
aList.add("仙林校区");
aList.add("仙林大学城");
aList.add("中山北路");
aList.stream().sorted(
Comparator.comparing(a->address.indexOf(a))
).forEach(System.out :: println);
也可以像下面这样不使用比较器:
aList.stream().sorted(
(a,b)->address.IndexOf(a)-address.IndexOf(b)
).forEach(System.out :: println);//由大到小排序
输出结果:

注:1.这里仙林大学城这个字段没有出现,所以序号是-1,被排在最前面。
2.Comparator.comparing();这个是比较器提供的一个方法,它返回的也是一个比较器,源码如下:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
2.1.2 筛选操作(filter):
上一步中,我们把一些字符串,按照在地址中出现的顺序排序。
接下来我们可能想要进行筛选,把不在地址中,但是indexof为“-1”,排在最前面的数据筛选掉:
filter可以对集合进行筛选,它的参数可以是一个lambda表达式,流中的数据将会通过该lambda表达式返回新的流。
这里Stream有一个特性很重要,它像一个管道,可以将多个操作连接起来,并只执行一次for循环,这样大大提高了效率,即使第二次的流操作需要第一次流操作的结果,时间复杂度也只有一个for循环:
于是我可以在前面加个filter(),这样把“-1”过滤掉:
String address = "中山北路南京大学仙林校区";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大学");
aList.add("仙林校区");
aList.add("仙林大学城");
aList.add("中山北路");
aList.stream().filter(a->address.indexOf(a)!=-1)
.sorted(
Comparator.comparing(a->address.indexOf(a))
).forEach(System.out :: println);
输出结果:

注:foreach是一个终端操作,参数也是一个函数,它会迭代中间操作完成后的每一个数据,这里它将每个不为空的元素打印出来。
其它的过滤操作还包括:
limit(long maxSize):获得指定数量的流。
distinct():通过hashCode和equals去除重复元素。
2.1.3 映射操作(map):
映射操作,就像一个管道,可以将流中的元素通过一个函数进行映射,返回一个新的元素。
这样遍历映射,最终返回一个新的容器,注意:这里返回的新容器数据类型可以不与原容器类型相同:
举个例子:我们将address中每个元素的位置找出,并返回一个int类型的存储位置信息的数组:
@Test
public void test() {
String address = "中山北路南京大学仙林校区";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大学");
aList.add("仙林校区");
aList.add("仙林大学城");
aList.add("中山北路");
List<Integer> aIntegers =aList.stream()
.map(str->mapFunc(address, str)).collect(Collectors.toList());
System.out.println(aIntegers);//.forEach(System.out :: println);
} private int mapFunc(String address,String str) {
return address.indexOf(str);
}
结果如下:

2.2规约操作(reduction ):
之前的中间操作只是对流中数据的处理,最终我们还是要将它们整合输出为一个结果,比如,返回一个最大值,返回一个新的数组,或者将所有元素进行分组等,这就是规约(末端)操作的作用。
我们常用的末端操作函数有Reduce()和collect();
2.2.1Reduce
reduce就是减少的意思,它会将集合中的所有值根据规则计算,最后只返回一个结果。
它有三个变种,输入参数分别是一个参数、二个参数以及三个参数;
1.一个参数的Reduce
它的参数就是一个函数接口:Optional<T> reduce(BinaryOperator<T> accumulator)
比如,我们找出数组中长度最大的一个数:
public void test() {
String address = "中山北路南京大学仙林校区";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大学");
aList.add("仙林校区");
aList.add("仙林大学城");
aList.add("中山北路");
Optional<String> a =aList.stream()
.reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2);
System.out.println(a.get());//仙林大学城
}
这里的Optional<T>就是一个容器,它可以避免空指针,具体可以百度,这里也可以返回一个String的。
2.两个参数的Reduce
T reduce(T identity, BinaryOperator<T> accumulator)
2个参数其实除了一个函数接口以外,还包括一个固定的初始化的值,它会作为容器的第一个元素进入计算过程:
例:将每个字符串拼接,并在之前加上“value:”:
public void test() {
String address = "中山北路南京大学仙林校区";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大学");
aList.add("仙林校区");
aList.add("仙林大学城");
aList.add("中山北路");
String t="value:";
String a =aList.stream()
.reduce(t, new BinaryOperator<String>() {
@Override
public String apply(String s, String s2) {
return s.concat(s2);
}
});
System.out.println(a);
}
结果如下:

3.三个参数的情况主要是在并行(parallelStream)情况下使用:可以参考(https://blog.csdn.net/icarusliu/article/details/79504602),有需要可以了解下。
2.2.2Collect
collect是一个非常常用的末端操作,它本身的参数很复杂,有3个:
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner);
还好,考虑到我们日常使用,java8提供了一个收集器(Collectors),它是专门为collect方法量身打造的接口:
我们常常使用collect将流转换成List,Map或Set:
1.转换成list:
Stream<String> stream = Stream.of("I", "love", "you", "too");
List<String> list = stream.collect(Collectors.toList());
2.转换成Map:
我们可以使用Collector.toMap()接口:Collectors.toMap(keyMapper, valueMapper),这里就需要我们指定key和value分别是什么。
例:我们将数组中的字符串作为key,字符串长度作为value,生成一个map:
String address = "中山北路南京大学仙林校区";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大学");
aList.add("仙林校区");
aList.add("仙林大学城");
aList.add("中山北路");
String t="value:"; Map<String, Integer> maps =
aList.stream().collect(Collectors.toMap(Function.identity(), String::length));
System.out.println(maps);
打印结果:
{中山北路=4, 大学=2, 仙林大学城=5, 仙林校区=4, 南京=2}
通常,我们在进行分组操作的时候也会将容器转换为Map,这里也说明一下:Collectors.groupingBy(classifier)
groupingBy与sql的group by类似,就是一个分组函数,
例:我们将数组中的字符串按长度分组:
String address = "中山北路南京大学仙林校区";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大学");
aList.add("仙林校区");
aList.add("仙林大学城");
aList.add("中山北路");
String t="value:"; Map<Integer, List<String>> maps =
aList.stream().collect(Collectors.groupingBy(String::length));
System.out.println(maps);
打印结果:
{2=[南京, 大学], 4=[仙林校区, 中山北路], 5=[仙林大学城]}
其他的末端操作api:
findFirst:返回第一个元素,常与orElse一起用: Stream.findFirst().orElse(null):返回第一个,如果没有则返回null
allMatch:检查是否匹配所有元素:Stream.allMatch(str->str.equals("a"))
anyMatch:检查是否至少匹配一个元素.
3.Stream的特性
1.中间操作惰性执行:一个流后面可以跟随0到多个中间操作,主要目的是打开流,并没有真正的去计算,而是做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历,并没有消耗资源。
2.流的末端操作只能有一次: 当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。之后如果想要操作就必须新打开流。
关于流被关闭不能再操作的异常:
这里曾经遇到过一个错误:stream has already been operated upon or closed
意思是流已经被关闭了,这是因为当我们使用末端操作之后,流就被关闭了,无法再次被调用,如果我们想重复调用,只能重新打开一个新的流。
java8 Stream常用方法和特性浅析的更多相关文章
- Java8 Stream新特性详解及实战
Java8 Stream新特性详解及实战 背景介绍 在阅读Spring Boot源代码时,发现Java 8的新特性已经被广泛使用,如果再不学习Java8的新特性并灵活应用,你可能真的要out了.为此, ...
- java8 Stream的实现原理 (从零开始实现一个stream流)
1.Stream 流的介绍 1.1 java8 stream介绍 java8新增了stream流的特性,能够让用户以函数式的方式.更为简单的操纵集合等数据结构,并实现了用户无感知的并行计算. 1.2 ...
- 对Java8 stream的简单实践
最近学习很多Java8方面的新特性,特地做了一些简单的实践和总结. import java.util.*; import java.util.stream.Collectors; public cla ...
- 简洁又快速地处理集合——Java8 Stream(下)
上一篇文章我讲解 Stream 流的基本原理,以及它与集合的区别关系,讲了那么多抽象的,本篇文章我们开始实战,讲解流的各个方法以及各种操作 没有看过上篇文章的可以先点击进去学习一下 简洁又快速地处理集 ...
- Java基础学习总结(33)——Java8 十大新特性详解
Java8 十大新特性详解 本教程将Java8的新特新逐一列出,并将使用简单的代码示例来指导你如何使用默认接口方法,lambda表达式,方法引用以及多重Annotation,之后你将会学到最新的API ...
- Java8 Stream性能如何及评测工具推荐
作为技术人员,学习新知识是基本功课.有些知识是不得不学,有些知识是学了之后如虎添翼,Java8的Stream就是兼具两者的知识.不学看不懂,学了写起代码来如虎添翼. 在上篇<Java8 Stre ...
- Java8 Stream终端操作使用详解
话不多说,自己挖的坑自己要填完,今天就给大家讲完Java8中Stream的终端操作使用详解.Stream流的终端操作主要有以下几种,我们来一一讲解. forEach() forEachOrdered( ...
- 如何通过 IntelliJ IDEA 来提升 Java8 Stream 的编码效率
本文翻译整理自:https://winterbe.com/posts/2015/03/05/fixing-java-8-stream-gotchas-with-intellij-idea 作者:@Wi ...
- 【转】Java8 Stream 流详解
当我第一次阅读 Java8 中的 Stream API 时,说实话,我非常困惑,因为它的名字听起来与 Java I0 框架中的 InputStream 和 OutputStream 非常类似.但是 ...
随机推荐
- matplotlib简单的使用(二)
1.折线图 import matplotlib as mlb from matplotlib import pylab as pl # 折线图 # 分别创建x,y坐标 x = [1,3,5,7,6,9 ...
- VM虚拟机安装centos详细图文教程
本教程贴,采用VM虚拟机进行安装, Ps:不懂VM使用的,可以百度一下 第一步,启动虚拟机,并进行新建---虚拟机·· 选择 从镜像安装,吧里有6.3镜像下载的链接的 然后, 下一步 . 选择客户机版 ...
- Android 打造编译时注解解析框架 这只是一个开始
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/43452969 ,本文出自:[张鸿洋的博客] 1.概述 记得很久以前,写过几篇博客 ...
- 学习笔记1--响应式网页+Bootstrap起步+全局CSS样式
一.学习之前要了解一些背景知识: 在2g时代,3g时代,4g时代,早期的网页浏览设备,功能机,智能机.(本人最喜欢的透明肌,和古典黑莓机) 1.什么是响应式网页? Responsive Web Pag ...
- 向combobox控件中添加元素
函数定义: bool FillComboBox(CComboBox* pc, CStringList& slValues, bool bOnlyUniqueValues = false); 函 ...
- Hadoop问题:DataNode进程不见了
DataNode进程不见了 问题描述 最近配置Hadoop的时候出现了这么一个现象,启动之后,使用jps命令之后是这样的: 看不到DataNode进程,但是能够正常的工作,是不是很神奇啊? 在一番 ...
- 【bzoj 3601】一个人的数论 (莫比乌斯反演+伯努利数)
题解: (吐槽:网上题解那个不严谨猜测真是没谁了……关键是还猜得辣么准……) 直接化简到求和那一段: $f_{d}(n)=\sum_{t|n}\mu(t)t^{d}\sum_{i=1}^{\frac{ ...
- BZOJ_1433_[ZJOI2009]假期的宿舍_二分图匹配
BZOJ_1433_[ZJOI2009]假期的宿舍_二分图匹配 题意: 学校放假了······有些同学回家了,而有些同学则有以前的好朋友来探访,那么住宿就是一个问题.比如A 和B都是学校的学生,A要回 ...
- 用Service+Broadcast解决倒计时过程中Activity被销毁的问题
主要思想是这样的:将倒计时CountDownTimer放在Service里面进行,每过一秒就一条发广播,在主Activity里注册广播,收到广播后更新UI. 一.写一个类CodeTimerServic ...
- mip-link 组件功能升级说明
背景描述 某个页面被多少页面引用(在其他页面上有指向这个页面的 a 标签),是搜索引擎判断这个页面价值的其中一个因子.这里的搜索引擎不只是指百度,还包括国内外其他的搜索引擎. MIP 在最初设计 MI ...