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:我们依旧有更简便的方法 想了解就来小编主页看看吧
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。