说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端,每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是,循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始
文章目录
- 1. 循环遍历与Stream对比
- 2. 流式思想概述
- 2.1 根据Collection获取流
- 2.2 根据Map获取流
- 2.3 根据数组获取流
- 3. 常用方法
- 3.1 filter-count-limit
- 4. 综合练习Stream
1. 循环遍历与Stream对比
- 第一步筛选出所有姓张的人
- 第二步筛选出名字中有三个字的人
- 第三步打印输出结果
传统循环遍历
package ListStream;
import java.util.ArrayList;
import java.util.List;
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-05-21 10:53
*/
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
}
for (String name : shortList) {
System.out.println(name);
}
}
}
如果希望对集合中的元素进行筛选过滤:
- 将集合A根据条件一过滤为子集B
- 然后再根据条件二过滤为子集C
Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?
package ListStream;
import java.util.ArrayList;
import java.util.List;
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-05-21 10:53
*/
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
// 仅仅一行代码就搞定了! 获取流、过滤姓张、过滤长度为3、逐一打印
list.stream().filter(s -> s.startsWith("张")).
filter(s -> s.length() == 3).forEach(System.out::println);
}
}
直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中
2. 流式思想概述
整体来看,流式思想类似于工厂车间的“生产流水线”,当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤 方案,然后再按照方案去执行它
这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字 3是最终结果
这里的 filter 、 map 、 skip
都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count
执行的时候,整个模型才会按照指定策略执行操作,而这得益于Lambda的延迟执行特性
2.1 根据Collection获取流
首先, java.util.Collection
接口中加入了default 方法 stream
用来获取流,所以其所有实现类均可获取流
package ListStream;
import java.util.*;
import java.util.stream.Stream;
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-05-21 10:53
*/
public class StreamDemo {
public static void main(String[] args) {
// list 获取流
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
// set 获取流
Set<String> set = new HashSet<>();
Stream<String> stream1 = set.stream();
// Vector 获取流
Vector<String> vector = new Vector<>();
Stream<String> stream2 = vector.stream();
}
}
2.2 根据Map获取流
java.util.Map 接口不是 Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况
package ListStream;
import java.util.*;
import java.util.stream.Stream;
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-05-21 10:53
*/
public class StreamDemo {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
// 根据键取流
Stream<String> keyStream = map.keySet().stream();
// 根据值取流
Stream<String> valueStream = map.values().stream();
// 根据键值对取流
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}
}
2.3 根据数组获取流
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of
package ListStream;
import java.util.*;
import java.util.stream.Stream;
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-05-21 10:53
*/
public class StreamDemo {
public static void main(String[] args) {
String[] array = {"张无忌", "张翠山", "张三丰", "张一元"};
Stream<String> stream = Stream.of(array);
}
}
3. 常用方法
流模型的操作很丰富,这里介绍一些常用的API,这些方法可以被分成两种:
- 终结方法: 返回值类型不再是
Stream
接口自身类型的方法,因此不再支持类似StringBuilder
那样的链式调用,终结方法包括count 和 forEach
方法 - 非终结方法: 返回值类型仍然是
Stream
接口自身类型的方法,因此支持链式调用,(除了终结方法外,其余方法均为非终结方法)
3.1 filter-count-limit
可以通过 filter
方法将一个流转换成另一个子集流,该接口接收一个 Predicate
函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件
- 过滤 : filter
- 统计个数: count
- 取用前几个: limit
- 跳过前几个: skip
- 映射: map
- 组合: concat
- 逐一处理: forEach
复习Predicate接口 : java基础之–常用函数式接口Predicate与Function
package ListStream;
import java.util.*;
import java.util.stream.Stream;
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-05-21 10:53
*/
public class StreamDemo {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
// 通过Lambda表达式来指定了筛选的条件:必须姓张
Stream<String> result = original.filter(s -> s.startsWith("张"));
// 取用前两个 limit 方法可以对流进行截取,只取用前n个 ,如果集合当前长度大于参数则进行截取;否则不进行操作
Stream<String> limit = original.limit(2);
// 跳过前几个 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流
Stream<String> skip = original.skip(2);
// 统计个数 count
System.out.println(result.count());
// 映射map 如果需要将流中的元素映射到另一个流中,可以使用 map 方法
// map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为 Integer 类对 象)
Stream<Integer> mapResult = original.map(Integer::parseInt);
// 如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
Stream<String> original2 = Stream.of("张三", "张翠山");
Stream<String> concatResult = Stream.concat(original, original2);
System.out.println(concatResult);
// 该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理
concatResult.forEach(System.out::println);
}
}
4. 综合练习Stream
- 第一个队伍只要名字为3个字的成员姓名
- 第一个队伍筛选之后只要前3个人
- 第二个队伍只要姓张的成员姓名
- 第二个队伍筛选之后不要前2个人
- 将两个队伍合并为一个队伍
- 根据姓名创建 Person 对象
- 打印整个队伍的Person对象信息
两个队伍集合代码如下
package ListStream;
import java.util.ArrayList;
import java.util.List;
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-05-21 13:14
*/
public class Demo {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
}
}
Person类
package ListStream;
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-05-21 13:14
*/
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
代码实现
package ListStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-05-21 13:14
*/
public class Demo {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
// 第一个队伍 姓名长度3 只要前三个
Stream<String> oneResult = one.stream().filter(s -> s.length() == 3).limit(3);
// 第二个队伍只要姓张成员 筛选后不要前两个
Stream<String> twoResult = two.stream().filter(s -> s.startsWith("张")).skip(2);
// 创建Person对象 打印
Stream.concat(oneResult, twoResult).map(Person::new).forEach(System.out::println);
}
}