Java8中的Stream流式操作 - 入门篇
作者:汤圆
个人博客:javalover.cc
前言
之前总是朋友朋友的叫,感觉有套近乎的嫌疑,所以后面还是给大家改个称呼吧
因为大家是来看东西的,所以暂且叫做官人吧(灵感来自于民间流传的四大名著之一)
官人们好啊,我是汤圆,今天给大家带来的是《Java8中的Stream流式操作 - 入门篇》,希望有所帮助,谢谢
文章纯属原创,个人总结难免有差错,如果有,麻烦在评论区回复或后台私信,谢啦
简介
流式操作也叫做函数式操作,是Java8新出的功能
流式操作主要用来处理数据(比如集合),就像泛型也大多用在集合中一样(看来集合这个小东西还是很关键的啊,哪哪都有它)
下面我们主要用例子来介绍下,流的基操(建议先看下lambda表达式篇,里面介绍的lambda表达式、函数式接口、方法引用等,下面会用到)
先来看下目录
目录
流是什么
老板,上栗子
流的操作步骤
流的特点
流式操作和集合操作的区别
正文
1. 流是什么
流是一种以声明性的方式来处理数据的API
什么是声明性的方式?
就是只声明,不实现,类似抽象方法(多态性)
2. 老板,上栗子
下面我们举个栗子,来看下什么是流式操作,然后针对这个栗子,引出后面的相关概念
需求:筛选年龄大于1的猫(猫的1年≈人的5年),并按年龄递增排序,最后提取名字单独存放到列表中
public class BasicDemo {
public static void main(String[] args) {
// 以下猫的名字均为真名,非虚构
List<Cat> list = Arrays.asList(new Cat(1, "tangyuan"), new Cat(3, "dangdang"), new Cat(2, "milu"));
// === 旧代码 Java8之前 ===
List<Cat> listTemp = new ArrayList<>();
// 1. 筛选
for(Cat cat: list){
if(cat.getAge()>1){
listTemp.add(cat);
}
}
// 2. 排序
listTemp.sort(new Comparator<Cat>() {
@Override
public int compare(Cat o1, Cat o2) {
// 递增排序
return Integer.compare(o1.getAge(), o2.getAge());
}
});
// 3. 提取名字
List<String> listName = new ArrayList<>();
for(Cat cat: listTemp){
listName.add(cat.getName());
}
System.out.println(listName);
// === 新代码 Java8之后 ===
List<String> listNameNew = list.stream()
// 函数式接口 Predicate的 boolean test(T t)抽象方法
.filter(cat -> cat.getAge() > 1)
// lambda表达式的方法引用
.sorted(Comparator.comparingInt(Cat::getAge))
// 函数式接口 Funtion的 R apply(T t)抽象方法
.map(cat-> cat.getName())
// 收集数据,把流转为集合List
.collect(Collectors.toList());
System.out.println(listNameNew);
}
}
class Cat{
int age;
String name;
public Cat(int age, String name) {
this.age = age;
this.name = name;
}
// 省略getter/setter
}
可以看到,用了流式操作,代码简洁了很多(秒哇)
Q:有的官人可能会想,这跟前面lambda表达式的组合操作有点像啊。
A:你说对了,确实只是像,区别还是很大的。因为lambda表达式的组合操作其实还是属于直接针对集合的操作;
文末会讲到直接操作集合和流式操作的区别,这里先跳过
下面我们基于这个栗子,来分别介绍涉及到的知识点
3. 流的操作步骤
我们先忽略旧版的集合操作(后面介绍流和集合的区别时再说),先来介绍流的操作(毕竟流才是今天的主角嘛)
流的操作分三步走:创建流、中间操作、终端操作
流程如下图:
这里我们要关注一个很重要的点:
在终端操作开始之前,中间操作不会执行任何处理,它只是声明执行什么操作;
你可以想象上面这个流程是一个流水线:我们这里做个简化处理
- 目的:先告诉你,我们要加工瓶装的水(先创建流,告诉你要处理哪些数据)
- 再针对这些瓶子和水,来搭建一个流水线:固定瓶子的夹具、装水的水管、拧盖子的爪子、装箱的打包器(中间操作,声明要执行的操作)
- 最后按下启动按钮,流水线开始工作(终端操作,开始根据中间操作来处理数据)
因为每一个中间操作都是返回一个流(Stream),这样他们就可以一直组合下去(我好像吃到了什么东西?),但是他们的组合顺序是不固定的,流会根据系统性能去选择合适的组合顺序
我们可以打印一些东西来看下:
List<Cat> list = Arrays.asList(new Cat(1, "tangyuan"), new Cat(3, "dangdang"), new Cat(2, "milu"));
List<String> listNameNew = list.stream()
.filter(cat -> {
System.out.println("filter: " + cat);
return cat.getAge() > 1;
})
.map(cat-> {
System.out.println("map:" + cat);
return cat.getName();
})
.collect(Collectors.toList());
输出如下:
filter: Cat{age=1}
filter: Cat{age=3}
map:Cat{age=3}
filter: Cat{age=2}
map:Cat{age=2}
可以看到,中间操作的filter和map组合到一起交叉执行了,尽管他们是两个独立的操作(这个技术叫作循环合并)
这个合并主要是由流式操作根据系统的性能来自行决定的
既然讲到了循环合并,那下面捎带说下短路技巧
短路这个词大家应该比较熟悉(比如脑子短路什么的),指的是本来A->B->C是都要执行的,但是在B的地方短路了,所以就变成了A->C了
这里的短路指的是中间操作,由于某些原因(比如下面的limit),导致只执行了部分,没有全部去执行
我们来修改下上面的例子(加了一个中间操作limit):
List<Cat> list = Arrays.asList(new Cat(1, "tangyuan"), new Cat(3, "dangdang"), new Cat(2, "milu"));
List<String> listNameNew = list.stream()
.filter(cat -> {
System.out.println("filter: " + cat);
return cat.getAge() > 1;
})
.map(cat-> {
System.out.println("map:" + cat);
return cat.getName();
})
// 只加了这一行
.limit(1)
.collect(Collectors.toList());
输出如下:
filter: Cat{age=1}
filter: Cat{age=3}
map:Cat{age=3}
可以看到,因为limit(1)只需要一个元素,所以filter过滤时,只要找到一个满足条件的,就会停止过滤操作(后面的元素就放弃了),这个技巧叫做短路技巧
这个就很大程度上体现了中间操作的组合顺序带来的优点:需要多少,处理多少,即按需处理
4. 流的特点
特点有三个:
- 声明性:简洁,易读,代码行数大大减少(每天有代码行数要求的公司除外)
- 可复合:更灵活,各种组合都可以(只要你想,只要流有)
- 可并行:性能更好(不用我们去写多线程,多好)
5. 流式操作和集合操作的区别:
现在我们再来回顾下开头例子中的集合操作:筛选->排序->提取
List<Cat> listTemp = new ArrayList<>();
// 1. 筛选
for(Cat cat: list){
if(cat.getAge()>1){
listTemp.add(cat);
}
}
// 2. 排序
listTemp.sort(new Comparator<Cat>() {
@Override
public int compare(Cat o1, Cat o2) {
// 递增排序
return Integer.compare(o1.getAge(), o2.getAge());
/**
* Q:为啥不用减法 return o1.getAge() - o2.getAge()?
* A:因为减法会有数据溢出的风险
* 如果o1.getAge()为20亿,o2.getAge()为-2亿,那么结果就会超过int的极限21亿多
**/
}
});
// 3. 提取名字
List<String> listName = new ArrayList<>();
for(Cat cat: listTemp){
listName.add(cat.getName());
}
System.out.println(listName);
可以看到跟流式操作不一样的有两点:
- 集合操作中有一个listTemp临时变量(流式操作没),
- 集合操作一直都在处理数据(而流式操作是直到最后一步的终端操作才会去处理数据),依次筛选->排序->提取名字,是顺序执行的
下面我们用表格来列出区别,应该会直观点
流式操作 | 集合操作 | |
---|---|---|
功能 | 处理数据为主 | 存储数据为主 |
迭代方式 | 内部迭代(只迭代一次),只需声明,不需要实现,流内部自己有实现) | 外部迭代(可一直迭代)需要自己foreach |
处理数据 | 直到终端操作,才会开始真正处理数据(按需处理) | 一直都在处理数据(全部处理) |
用生活中的例子来对比的话,可以用电影来比喻
流就好比在线观看,集合就好本地观看(下载到本地)
总结
- 流是什么:
- 流是一种以声明性的方式来处理数据的API
- 流是从支持数据处理操作的源生成的元素序列
- 源:数据的来源,比如集合,文件等(本节只介绍了集合的流式操作,因为用的比较多;后面有空再介绍其他的)
- 数据处理操作:就是流的中间操作,比如filter, map
- 元素序列:通过流的终端操作,返回的结果集
- 流的操作流程:
- 创建流 -> 中间操作 -> 终端操作
- 中间操作只是声明,不真实处理数据,直到终端操作开始才会执行
- 循环合并:中间操作会自由组合(流根据系统自己来决定组合的顺序)
- 短路技巧:如果中间操作处理的数据已经达到需求,则会立即停止处理数据(比如limit(1),则当处理完1个就会停止处理)
- 流式操作和集合操作的区别:
- 流按需处理,集合全处理
- 流主攻数据处理,集合主攻数据存储
- 流简洁,集合不
- 流内部迭代(只迭代一次,完后流会消失),集合外部迭代(可一直迭代)
后记
最后,感谢大家的观看,谢谢
原创不易,期待官人们的三连哟
Java8中的Stream流式操作 - 入门篇的更多相关文章
- Java8——Stream流式操作的一点小总结
我发现,自从我学了Stream流式操作之后,工作中使用到的频率还是挺高的,因为stream配合着lambda表达式或者双冒号(::)使用真的是优雅到了极致!今天就简单分(搬)享(运)一下我对strea ...
- Java的Stream流式操作
前言 最近在实习,在公司看到前辈的一些代码,发现有很多值得我学习的地方,其中有一部分就是对集合使用Stream流式操作,觉得很优美且方便.所以学习一下Stream流,在这里记录一下. Stream是什 ...
- 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?
写在前面 先说点题外话:不少读者工作几年后,仍然在使用Java7之前版本的方法,对于Java8版本的新特性,甚至是Java7的新特性几乎没有接触过.真心想对这些读者说:你真的需要了解下Java8甚至以 ...
- Java8新特性 Stream流式思想(二)
如何获取Stream流刚开始写博客,有一些不到位的地方,还请各位论坛大佬见谅,谢谢! package cn.com.zq.demo01.Stream.test01.Stream; import org ...
- Java8 新特性 —— Stream 流式编程
本文部分摘自 On Java 8 流概述 集合优化了对象的存储,大多数情况下,我们将对象存储在集合是为了处理他们.使用流可以帮助我们处理对象,无需迭代集合中的元素,即可直接提取和操作元素,并添加了很多 ...
- Java8新特性 Stream流式思想(三)
Stream接口中的常用方法 forEach()方法package cn.com.cqucc.demo02.StreamMethods.Test02.StreamMethods; import jav ...
- Java8新特性 Stream流式思想(一)
遍历及过滤集合中的元素使用传统方式遍历及过滤集合中的元素package cn.com.zq.demo01.Stream.test01.Stream; import java.util.ArrayLis ...
- java8中的stream流遍历
比较for循环.迭代器.java8Stream流遍历的不同 package cnom.test.testUtils; import java.io.Serializable; import java. ...
- 【Java8新特性】面试官:谈谈Java8中的Stream API有哪些终止操作?
写在前面 如果你出去面试,面试官问了你关于Java8 Stream API的一些问题,比如:Java8中创建Stream流有哪几种方式?(可以参见:<[Java8新特性]面试官问我:Java8中 ...
随机推荐
- JMM内存模型相关笔记整理
JMM 内存模型是围绕并发编程中原子性.可见性.有序性三个特征来建立的 原子性:就是说一个操作不能被打断,要么执行完要么不执行,类似事务操作,Java 基本类型数据的访问大都是原子操作,long 和 ...
- npm与package.json快速入门
本文转载自npm与package.json快速入门 导语 npm 是前端开发广泛使用的包管理工具,之前使用 Weex 时看了阮一峰前辈的文章了解了一些,这次结合官方文章总结一下,加深下理解吧! 读完本 ...
- C#如何防止程序多次运行的技巧(精典)
一.引言最近发现很多人在论坛中问到如何防止程序被多次运行的问题的,所以这里就记录下来,希望给遇到同样问题的朋友有所参考的,同时也是对自己的一个积累.在介绍具体实现代码之前,我们必须明确解决这个问题的思 ...
- js异步回调Async/Await与Promise区别 新学习使用Async/Await
Promise,我们了解到promise是ES6为解决异步回调而生,避免出现这种回调地狱,那么为何又需要Async/Await呢?你是不是和我一样对Async/Await感兴趣以及想知道如何使用,下面 ...
- FTP服务器搭建以及上传下载的学习
首先需要搭建FTP服务步骤如下: 1.在win7上先开启ftp服务:这里点击确定后,可能会要等一会儿,完成后有时系统会提示重启 2.打开 计算机-->管理--> 在这里我们可以看见 ...
- linux调度全景指南
- 第39天学习打卡(多线程 Thread Runnable 初始并发问题 Callable )
多线程详解 01线程简介 Process与Thread 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念. 进程则是执行程序的一次执行过程,它是一个动态的概念.是系统资源分配的 ...
- Qt+opencv亲自配置教程
了别人的配置,总是无法配置成功,自己慢慢摸索配置成功.我失败的原因是在于自己本机的环境变量和他们不同,特此记下,分享给有相同问题的朋友. 一.需要软件 1.cmake 3.11.3(版本无所谓) 2. ...
- HDOJ-2181(深搜记录路径)
哈密顿绕行世界问题 HDOJ-2181 1.本题是典型的搜索记录路径的问题 2.主要使用的方法是dfs深搜,在输入的时候对vector进行排序,这样才能按照字典序输出. 3.为了记录路径,我使用的是两 ...
- double型数据的输入和输出--%f和%lf
scanf函数是通过指针指向变量的. %f告诉scanf函数在所传地址位置上存储一个float型值, 而%lf告诉scanf函数在所传地址位置上存储一个double型值. 这里float和double ...