课程内容 1>Stream流
2>Stream流方法使用
教学目标 1>能够理解流与集合相比的优点
2>能够理解流的延迟执行特点
3>能够通过集合、映射或数组获取流
4>能够掌握常用的流操作
Stream流
概念
Stream流并不是Java 中IO流,而是Java8 之后新引入的一种数据流操作API,它的出现得益于Lambda函数编程方式引入,其目的是解决集合类库操作短板,让集合操作更加优雅。
集合短板
JDK8之前,集合操作的短板在于集合中元素处理模式较为死板,甚至过于僵化,当需要对集合元素进行灵活处理时,需要没完没了的遍历,上个例子喵喵
需求:花名册中找出数学100分学生
public class Student {
private String name;
private int math;
private int english;
public Student(String name, int math, int english){
this.name = name;
this.math = math;
this.english = english;
}
//省略get/set...
}
public class App {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(new Student("dafei", 100, 100));
list.add(new Student("xiaofei", 900, 100));
list.add(new Student("zhongfei", 100, 80));
list.add(new Student("laofei", 100, 100));
list.add(new Student("feifei", 60, 70));
List mathList = new ArrayList<>();
for (Student student : list) {
if(student.getMath() == 100){
mathList.add(student);
}
}
}
}
需求:花名册中找出数学100分,并且英语也100分的学生
public class App {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(new Student("dafei", 100, 100));
list.add(new Student("xiaofei", 900, 100));
list.add(new Student("zhongfei", 100, 80));
list.add(new Student("laofei", 100, 100));
list.add(new Student("feifei", 60, 70));
List mathList = new ArrayList<>();
for (Student student : list) {
if(student.getMath() == 100){
mathList.add(student);
}
}
//在第一个集合的基础上在做一次遍历
List allList = new ArrayList<>();
for (Student student : mathList) {
if(student.getEnglish() == 100){
allList.add(student);
}
}}
}
上面例子,list集合元素操作,避免不了遍历,甚至要多次遍历,这种结构代码一堆积,怎么看都不雅观。此时可以进一步思考,在集合操作中,我们在意的是集合遍历本身呢,还是集合中元素处理逻辑呢?答案很明显,那能不能忽略集合的遍历,突出元素的逻辑处理呢?JDK8的Stream流处理就是为了解决这种问题的。
Stream流将集合中的外部循环转换成内部循环,让使用者更关注集合元素本身处理逻辑
需求:花名册中找出数学100分,并且英语也100分的学生
public class App {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(new Student("dafei", 100, 100));
list.add(new Student("xiaofei", 900, 100));
list.add(new Student("zhongfei", 100, 80));
list.add(new Student("laofei", 100, 100));
list.add(new Student("feifei", 60, 70));
List allList = list.stream()
//过滤数学100的学生
.filter(student -> student.getMath() == 100)
//过滤英语100的学生
.filter(student -> student.getEnglish() == 100)
//汇总成集合
.collect(Collectors.toList());
}
}
上面代码与之前代码等价,但代码更加简洁啦,只需关注filter中的元素过滤逻辑即可。
流概念 Stream流有点类似现实生活中的快递运输带,或者工厂流水线上的履带。
文章图片
Stream并不是集合,本身不存储任何元素,它是集合元素的函数模型,用于加工元素的载具。你可以设想一种场景:工厂中有一条流水线线,产品从这一头传送到另外一头,此时在流程线上加上产品喷漆机器,然后再加上产品贴标机器,最后加上产品装箱机器等等,一个流程下来,就完成产品加工过程。
结合上面例子能悟出Stream流的功能么?
Stream核心功能:1>数据输入 2>数据加工 3>数据产出
//list数据输入
List allList = list.stream()
//数据加工
.filter(student -> student.getMath() == 100)
//数据产出
.collect(Collectors.toList());
Stream操作细节 Stream流是一个来自数据源(list/数组)的元素队列,下面是它具体操作细节
1>Stream流中元素都是特定的数据烈性,也就是泛型一致
2>Stream流不存储元素,仅仅为数据提供操作平台
3>Stream流的操作数据来自集合或者数组
【JDK新语法|一文让你掌握Stream流,还是那么自信】4>Stream流使用Pipeling管道操作模式,允许对Stream流中数据多次加工,每次加工结束都返回流对象本身,也即支持链式编程,需要注意,每次加工一次返回的流都是新流对象
5>Stream流使用内部遍历方式,区分与集合那种外部遍历。
6>Stream流操作步骤:获取(接入)数据源-->数据转换(数据加工)-->返回结果(数据产出)
获取Stream流 java.util.stream.Stream 是Java 8新加入的流接口。常用的方式:
1> Collection 集合使用 stream 默认方法获取流;
2>Stream 接口的静态方法 of 可以获取数组流。
Collection集合方式
List list = new ArrayList<>();
Stream listStream = list.stream();
Set set= new HashSet<>();
Stream setStream = set.stream();
Map map = new HashMap<>();
Stream keyStream = map.keySet().stream();
Stream valueStream = map.values().stream();
数组的方式
String[] array = new String[]{"a","b","c","d"};
Stream arrayStream1 = Stream.of("a", "b", "c", "d");
Stream arrayStream2 = Stream.of(array);
Stream流常用方法 Stream流常用的方法分为2种:
1>数据加工型方法。 加工方法返回类型还是Stream类型,可以使用链式方式调用。
2>终结型方法,返回对象值不在是Stream类型, 根据方法选择返回不同数据。
forEach
遍历,逐一迭代Stream流中数据,此方法为终结方法
void forEach(Consumer super T> action);
方法需要传入Consumer消费型接口,无返回
@FunctionalInterface
public interface Consumer {
void accept(T t);
}
案例:遍历给定的集合
List list = Arrays.asList("a", "b", "c", "d");
list.stream().forEarch(item->System.out.println(item))
filter
过滤,对数据进行过滤加工,返回新的子集流(加工之后流对象就不一样了),此方法为加工型方法
Stream filter(Predicate super T> predicate);
方法需要传入Predicate判断型接口,通过返回值true/false对流数据进行筛选
@FunctionalInterface
public interface Predicate {
boolean test(T t);
}
案例:挑选后缀名为png的文件
String[] files = {"a.txt", "b.png", "c.avi"};
Stream stream = Stream.of(files).filter(item->item.endsWith("png"));
map
映射,对数据进行映射加工,功能:将元素A转换成元素B格式,此方法为加工型方法
Stream map(Function super T, ? extends R> mapper);
方法需要传入Function转换型接口,转换后返回转换后的类型对象的流
需求:从学生对象集合中返回学生名称
List list = new ArrayList<>();
list.add(new Student("dafei", 100, 100));
list.add(new Student("xiaofei", 900, 100));
list.add(new Student("zhongfei", 100, 80));
list.add(new Student("laofei", 100, 100));
list.add(new Student("feifei", 60, 70));
Stream stream = list.stream().map(item -> item.getName());
flatMap
扁平化映射,将stream流中的每个元素转成流,然后将所有流进行汇总,得到新流,此方法为加工型方法
Stream flatMap(Function super T, ? extends Stream extends R>> mapper);
方法参数传入是转换型接口
@FunctionalInterface
public interface Function {
R apply(T t);
}
从方法参数限制中可以看出, 返回的R是Stream流类型, T则是流中的元素。
需求:将给出的数组集合,合并成一个Integer类型集合
List list = new ArrayList();
Integer[] a = {1,2,3,4};
Integer[] b = {5,6,7,8};
Integer[] c = {9,10,11,12};
list.add(a);
list.add(b);
list.add(c);
//返回的流: 1,2,3,4,5,6,7,8,9,10,11,12
Stream stream = list.stream().flatMap(item->Stream.of(item));
从代码上分析,flatMap传入Function函数接口对象, 后续肯定调用apply方法,参数是item,也就是list集合中Integer[] 数组, 然后执行Stream.of(item)方法,表示将数组转化成Stream流,最后将所有的Stream流再合并成最终的Stream
等价于:
Stream s = Stream.of();
for (Integer[] itemOut : list) {
//lambda转化函数对象,逻辑:将Integer[]-->Stream
Function> function = item->Stream.of(item);
//list集合中每个元素都转化成:Stream
Stream st = function.apply(itemOut);
//将所有的流拼接成一个流
s = Stream.concat(s, st);
}
distinct
去重,将Stream流中重复的元素排除掉,判断重复的标准是:hashCode与equals方法,此方法是加工型方法
Stream distinct();
需求:对给定的List集合进行排重
List ll = Arrays.asList(1,2,3,4,4,3,2,1);
Stream ss = ll.stream().distinct();
ss.forEach(System.out::println);
count
统计,对经过Stream流的数据进行统计,求个数,此方法为终结型方法
long count();
方法无返回值,执行后返回long类型数据
需求:求学生总数
List list = new ArrayList<>();
long count = stream.count();
limit
限制,或者叫截取, 对流进行截断,取前面几个数据形成新流,此方法为加工型方法
Stream limit(long maxSize);
方法传入需要截取的元素个数,然后返回新的子流
需求:查询数据成绩在前面3的学生
public class Student {
private String name;
private int math;
private int english;
}
List list = new ArrayList<>();
list.add(new Student("dafei", 100, 100));
list.add(new Student("xiaofei", 90, 100));
list.add(new Student("zhongfei", 100, 80));
list.add(new Student("laofei", 100, 100));
list.add(new Student("feifei", 60, 70));
Collections.sort(list, (o1,o2)->o2.getMath()-o1.getMath());
Stream limit = list.stream().limit(3);
limit.forEach(System.out::println);
skip
跳过,对Stream流中的数据执行跳过处理,此方法为加工型方法
Stream skip(long n);
需求:遍历list集合,跳过前面3个数据
List ll = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Stream ss = ll.stream().skip(3);
ss.forEach(System.out::println);
//4,5,6,7,8,9,10
sorted
第一种排序:对Stream流中数据进行自然排序,此方法为加工型方法
Stream sorted();
需求:遍历list集合,先排重,然后按自然顺序排序
List ll = Arrays.asList(3,8,9,4,7,5,8,3,7,4,8,9,5,0,0,7,1,2,3,8,9,4);
Stream ss = ll.stream().distinct().sorted();
ss.forEach(System.out::println);
第二种排序:对Stream流中数据按照指定的规则进行排序,此方法为加工型方法
Stream sorted(Comparator super T> comparator);
参数是一个Comparator比较器接口, compare方法的返回值决定元素排序顺序
返回值大于0表示大于
返回值小于0表示小于
返回值等于0表示等于
@FunctionalInterface
public interface Comparator {
int compare(T o1, T o2);
}
需求:遍历list集合,先排重,然后倒序排
List ll = Arrays.asList(3,8,9,4,7,5,8,3,7,4,8,9,5,0,0,7,1,2,3,8,9,4);
Stream ss = ll.stream().distinct().sorted((o1,o2)->o2-o1);
ss.forEach(System.out::println);
max/min
查最大/最小值,返回Optional对象,此方法为终结型方法
Optional max(Comparator super T> comparator);
Optional min(Comparator super T> comparator);
方法传入的参数为比较器,根据比较器定制判定规则。
需求:查询数学成绩最高的学生与英语成绩最低的学生
List list = new ArrayList<>();
list.add(new Student("dafei", 100, 100));
list.add(new Student("xiaofei", 90, 100));
list.add(new Student("zhongfei", 100, 80));
list.add(new Student("laofei", 100, 100));
list.add(new Student("feifei", 60, 70));
Optional max = list.stream().max((o1, o2) -> o2.getMath() - o1.getMath());
System.out.println(max.get());
Optional min = list.stream().max((o1, o2) -> o1.getEnglish() - o2.getEnglish());
System.out.println(min.get());
o1-o2 为正序比较,求最小值, o2-o1是逆序比较,求最大值
allMatch/anyMatch/noneMatch
数据匹配
allMatch:判断流中数据是否都满足指定的条件(所有都要)
anyMatch:判断流中数据是否存在满足指定条件(至少一个)
noneMatch:判断流中数据是否都不满足指定条件
上面方法都是终结型方法
boolean allMatch(Predicate super T> predicate);
boolean anyMatch(Predicate super T> predicate);
boolean noneMatch(Predicate super T> predicate);
方法参数使用Predicate判断型接口
需求:判断集合中数据是否都大于3
需求:判断集合种数据是否存在一个大于3
需求:判断集合种数据是否都不等于3
List list = Arrays.asList(1,2,3,4,5,6);
boolean ret = list.stream().allMatch(item -> item > 3);
//false
boolean ret2 = list.stream().anyMatch(item -> item > 3);
//true
boolean ret3 = list.stream().noneMatch(item -> item == 3);
//false
findFirst/findAny
查找
findFirst:查找流中第一个元素,此方法为终结型方法
findAny:查找流中任意一个元素,此方法为终结型方法
Optional findFirst();
Optional findAny();
需求:查询流中第一个元素
Optional first = list.stream().findFirst();
Optional any = list.stream().findAny();
concat
连接,流连接,将2个流连接成一个新的流,此方法为加工型方法
public static Stream concat(Stream extends T> a, Stream extends T> b)
注意,该方法是Stream接口的静态方法
需求:将集合1跟集合2合并
List listA = Arrays.asList(1,2,3,4,5,6);
List listB = Arrays.asList(7,8,9,10,11);
Stream concat = Stream.concat(listA.stream(), listB.stream());
collect
汇总,也可以理解为流转换,将流转换成指定类型数据,此方法为终结型方法
R collect(Collector super T, A, R> collector);
R collect(Supplier supplier, BiConsumer accumulator,BiConsumer combiner);
collect:将流转换为其他形式:list
collect:将流转换为其他形式:set
collect:将流转换为其他形式:map
collect:将流转换为其他形式:sum
collect:将流转换为其他形式:avg
collect:将流转换为其他形式:max
collect:将流转换为其他形式:min
List listA = Arrays.asList(1,2,3,4,5,6);
List list = listA.stream().collect(Collectors.toList());
Set set = listA.stream().collect(Collectors.toSet());
Map map = listA.stream().collect(Collectors.toMap(k->k, v->v));
int sum = listA.stream().collect(Collectors.summingInt(item -> item.intValue()));
double avg = listA.stream().collect(Collectors.averagingDouble(item -> item.doubleValue()));
intmax = listA.stream().collect(Collectors.maxBy((o1, o2)->o2-o1)).get();
intmin = listA.stream().collect(Collectors.minBy((o1, o2)->o1-o2)).get();
上面方法最核心逻辑还是Collectors 工具类, 可以将各种常用的逻辑转换成可以输出的收集对象:Collector,再由收集对象对流进行加工成成像样模式。
好到这,Stream流的基本操作就差不多了。
推荐阅读
- spring源码分析|spring初始化过程源码分析
- Java|JVM 类加载机制与加载过程
- intellij-idea|新建SSM项目 仅供参考 5.26
- java笔记|Windows 关闭8080端口(8080端口被占用)
- java|java list集合合并_JAVA List合并集合
- java|java 子节点查找父节点_现代化的 Java (二十)——撮合节点的实现
- Java|风雨java路之【基础篇】——看看Set集合那点儿猫腻
- SpringBoot|快速从零搭建一个SpringBoot Web项目
- java|Java集合框架————Map集合(1)