JDK8 中强大的 Stream
2013 年发布的 JDK8 可以说是深入人心
今天这篇文章主要讨论一下 JDK8 中的新特性Stream
Stream 的介绍
- Stream 被译为‘流’但他并非是 io 流那种传输流 而是是对集合 (Collection) 对象功能的增强 包括一些对集合和数组处理的 ‘S’ 操作
- 另外 Stream 包含了并行操作 把控得当的话会大大提高程序效率
- 个人感觉 JDK 从第八代 (或者再早一点) 就慢慢的开始研究并实现像 GoogleGuava、Hutool 等早些年就很有风采的库里面的一些代码
当然了我们 java 跟 C 的关系相必也不用多说了吧 哈哈哈(开个玩笑)
Stream 的基本使用
在这里我只解释比较常用的操作 尽量做到比较好的有效的讨论
(先抱好西瓜再起捡芝麻吧)
-
Stream 的创建
- Stream.of(…);(静态方法 创建有限长度)
- Collection.stream() (集合内的创建方式)返回串行流
-
Collection.parallelStream() 返回并行流
- 并行操作可以根据开发需求进行使用 效果比较明显
- Arrays.stream(数组引用)
// 通过 Stream 静态方法 of
Stream.of(1,2,3,"a","b","c");
// 通过集合
var list=new ArrayList();
list.stream();
// 通过数组(只能接受 int[] double[] long[])
int[] int={1,2,3,4,5};
Arrays.stream(int);
-
Stream 的操作步骤
- Intermediate(中级操作)
可以执行多次的操作 - Terminal(终极操作)
只能执行一次操作 必须有的操作 - Short-circuiting(短路操作)
只能执行一次
- Intermediate(中级操作)
Stream 的中端操作(Intermediate)
可以执行多次 对 Stream 中的元素过滤 排序等
虽然中端操作可以执行多层处理 但是并不是一个中端操作就遍历一次
可以认为转换操作是 lazy(惰性求值)的,只有在 Terminal 操作执行时,才会一次性执行
- filter 对 Stream 指定条件过滤
filter 传入的 Lambda 表达式必须是 Predicate 实例,
参数可以为任意类型,而其返回值必须是 boolean 类型。
class Stu{
private String name;
private int age;
private double money;
...
}
var list=List.of(new Stu("Asen",18,100.0),
new Stu("Bsen",28,2000.0),
new Stu("Csen",38,3000.0)
);
// 要求找出集合中存储 Stu 的年龄为 18 的元素并打印
list.stream().filter(s->s.getAge()==18).forEach(out::println);
// 一行代码解决是不是要比普通遍历爽的多
- distinct 对 Stream 中元素进行查重
int[] i={1,2,3,4,5,2,3,4};
// 对数组进行查除重复的元素
Arrays.stream(i).distinct().forEach(out::println);
//1,5
- map 有 mapToDouble,mapToInt,mapToLong 三种变形
即是对 Stream 中数据进行处理或转换类型
int[] i={1,2,3,4,5,2,3,4};
// 查重后对元素进行 *10
Arrays.stream(i).distinct()
.map(i->i*10).forEach(out::println);
//10,50
// 将数组中数据转换为 double 类型
Arrays.stream(i).distinct()
.mapToDouble(i->i).forEach(out::println);
//1.0,5.0
- flatMap 传入的 Lambda 表达式必须是 Function 实例
参数可以为任意类型,而其返回值类型必须是一个 Stream
Stream.of(1,1,1,1)
.flatMap(v->Stream.of(v*10)).forEach(out::println);
//10 10 10 10
- concat 对两个 Stream 进行拼接
若有一个 Stream 是并行的,则新的 Stream 也是并行的
Stream.concat(Stream.of(1,2,3),Stream.of(4,5))
.forEach(out::println);
// 打印结果
// 1 2 3 4 5
- skip 去掉元素中前几个元素
- peek 创建一个包含 Stream 中所有元素的 新 Stream
并且还可以提供一个新的消费函数 此消费函数会先执行
Stream.of(1, 2, 3, 4, 5)
.skip(3)
.peek(integer -> out.println("accept:" + integer))
.forEach(out::println);
// accept:4
// 4
// accept:5
// 5
- sorted 对元素进行排序
一个重载方法 有参需要传入指定规则 如:Integer::compareTo
Stream.of(1, 3, 4, 2, 5)
.sorted()
.forEach(out::println);
//1 2 3 4 5
Stream.of(1, 3, 4, 2, 5)
.sorted(Comparator.reverseOrder())
.forEach(out::println);
//5 4 3 2 1
Stream 的终端操作(Terminal)
中端操作只能执行一次 将值返回 或者输出内容
并且 Stream 不能被二次利用如:二次打印 二次转换
- forEachOrdered forEach 前面一直在用的终端操作否则看不到结果
前者按照元素插入顺序进行遍历
后者如果 Stream 指定了顺序则按照指定顺序 - count 返回元素个数
vat int=Stream.of(1,2,3,4).count();
//int = 4
- max min 根据指定的 Comparator 进行查找最大或最小值
返回一个 Optional,该 Optional 中的 value 值就是 Stream 中最小的元素
两个完全根据规则而定 所以 max 也可以找出最小值 min 也可以找出最大值
var list=List.of(1,2,3,2,4);
var max=list.stream().max((o1,o2)->o2-o1);
// 在 max 中设置规则找出最小值 o=1
var min=list.stream().min((o1,o2)->o1-o2);
// 在 min 中设置找出最大值
- 对数据的简单处理:
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3,4).reduce(0,Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3,4).reduce(Integer::sum).get();
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0,-3.0,-2.0)
.reduce(Double.MAX_VALUE, Double::min);
- collect 是一个终端操作 同时也可以认为是一个高端操作
比如对 Stream 中的元素生成集合 栗子如下: - 生成 List toList
var tolist=Stream.of(2,1,3,2,4,3)
.distinct()
.sorted()
.collect(Collectors.toList());
out.println(tolist);// [1, 2, 3, 4]
默认为 ArrayList
- 生成 set 头 toSet
var toset=Stream.of("a","b","c","2")
.distinct()
.sorted()
.collect(Collectors.toSet());
out.println(toset);//[a, 2, b, c]
默认为 HashSet
- 如果想指定生成集合类型就要用到 toCollection
var collectors=Stream.of(1,2,3,4,5)
.collect(Collectors.toCollection(ArrayList::new));
- 生成 map
对 map 集合的生成可想而知是比较复杂的 而且在转换时会出现一些问题 不能像正常 map 一样使用 但是我们可以解决
Student[] stu= {new Student("Asen", 02, 10000),
new Student("Byin", 01, 2000),
new Student("Dfei", 03, 2000),
new Student("Cjie", 03, 300)};
// 把学生的 ID 作为键 name 作为值
// 此时按照普通 map 的创建的话 ID=03 的新值会将旧值覆盖
// 但是按照 toMap 则要进行处理
var stumap=Arrays.stream(stu)
.sorted(comparing(Student::id))
.collect(Collectors.toMap(Student::id, Student::name));
// 这种情况肯定是报错的 下面具体说明解决办法
- toMap 的大坑之一:如果有相同的值则会抛出异常
解决办法:
Student[] stu= {new Student("Asen", 02, 10000),
new Student("Byin", 01, 2000),
new Student("Dfei", 03, 2000),
new Student("Cjie", 03, 300)};
// 这时候要传入第三个参数 代表新值覆盖旧值
var stumap=Arrays.stream(stu)
.sorted(comparing(Student::id))
.collect(Collectors.toMap(Student::id, Student::name,(x,l)->l));
// 但是大家有没有想过 如果值为 null 呢 当然也是不能正常使用的下面具体说明
- toMap 的大坑之二:如果值为 null 也会抛出异常
解决办法:
var nullmap=Arrays.stream(stu)
.sorted(comparing(Student::id))
.collect(Collectors
.toMap(Student::id, s-> s.name()==null?"":s.name(),(l,x)->x));
// 简单的对值进行判断 如果为 null 则替换为指定字符
- Collectors.joining 将 Stream 转换为字符串
// 如果为纯字符串类型
Stream.of("1","2","3","4").collect(Collectors.joining(","));
// 如果为数值类型
list.stream().map(r -> r.toString()).collect(Collectors.joining(","));
ps: 如果想要更简便的集合与字符串的相互转换 可以继续跟进小编哦
Stream 的短路操作 Short-circuiting
- allMatch 判断断 Stream 中的元素是否 全部 满足指定条件 如果全部满足条件返回 true 否则返回 false
boolean allMatch=Stream.of(1,2,3,4,5)
.allMatch(i->i>0);
// allMatch=true
- anyMatch 判断元素中是否最少有一个满足条件 有一个就返回 true 全部不满足则返回 false
boolean anyMatch=Stream.of(1,2,3,4,5)
.allMatch(i->i<0);
// anyMatch=false
- noneMatch 判断 Stream 中元素是否全部不满足指定条件 全不满足返回 true 反之返回 false
boolean noneMatch=Stream.of(1,2,3,4,5)
.noneMatch(i->i<0);
// noneMatch=true
- limit 从 Stream 中截取指定个数的元素 如果 Stream 元素个数小于指定个数 则返回全部
Stream.of(1,2,3,4,5)
.limit(3).forEach(out::println);
// 1,2,3
Stream 扩招操作
关于 Stream 的常用操作就讲到这里了
下面说一些稍微高级一点的 ’sao’ 操作
- 请看题 1:要求对一个字符串挑出其中的英文字母的个数
普通方式我们不做具体说明
// 普通方法应该就是遍历然后判断了吧 但是我们可以利用并行操作进行高效查找 并且这样的链式结构他看着不爽吗 哈哈哈
String st="12asbs*346";
var vi=toList(convert(char[].class,st)).parallelStream()
.flatMap(vv->Stream.of((char)vv))
.filter(ss->ss>='A'&&ss<='Z'||ss>='a'&&ss<='z').count();
// vi=4
// 当然这种方法显然也是比较冗余的 但是多一种思路总不算坏吧
//ps 如果想了解更简便的方法 请继续跟进小编博客
-
请看题 2:要求对 Stream 中元素按照奇数偶数分组
当然了 大家在实际应用中可以结合数组 字符串操作- 补充 partitioningBy 返回 map 集合 数据分组
只能按照 Boolean 进行分组 也就是说 partitioningBy 传入的 lambda 表达式只能是返回 Boolean 类型
- 补充 partitioningBy 返回 map 集合 数据分组
var map=Stream.of(1,2,3,4,5)
.collect(Collectors.partitioningBy(it->it%2==0));
//{false=[1, 3, 5], true=[2, 4]}
var map3=Stream.of(1,2,3,4,5)
.collect(Collectors.partitioningBy(it->it%2==0,Collectors.counting()));
//{false=3, true=2}
-
请看题 3:要求对 Stream 中元素统计每个元素出现的个数 娟姐的小徒弟们你们想到了啥 嗯 -_- 哈哈哈哈
- 补充 groupingBy 返回 map 也属于数据分组
但是按照任意类型进行分组
- 补充 groupingBy 返回 map 也属于数据分组
var map=Stream.of("1","2","a","a","3","b","2")
.sorted()
.collect(Collectors.groupingBy(ic->ic,Collectors.counting()));
map.forEach((k,v)->out.println(k+"出现的次数"+v));
/* 1 出现的次数 1
2 出现的次数 2
3 出现的次数 1
a 出现的次数 2
b 出现的次数 1 */
ps:我们依旧有更简便的方法 想了解就来小编主页看看吧