作者:汤圆
集体博客: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 个就会进行解决)
-
流式操作和汇合操作的区别:
- 流 按需解决 ,汇合 全解决
- 流主攻 数据处理 ,汇合主攻 数据存储
- 流 简洁 ,汇合 不
- 流 外部迭代 (只迭代一次,完后流会隐没),汇合 内部迭代(可始终迭代)
后记
最初,感激大家的观看,谢谢
原创不易,期待官人们的三连哟