JDK新语法|一文让你掌握Stream流,还是那么自信

课程内容 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流有点类似现实生活中的快递运输带,或者工厂流水线上的履带。
JDK新语法|一文让你掌握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 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 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 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> 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 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 comparator); Optional min(Comparator 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 predicate); boolean anyMatch(Predicate predicate); boolean noneMatch(Predicate 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 a, Stream 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 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流的基本操作就差不多了。

    推荐阅读