Stream流简单记录
在本文中将记录自主学习使用Stream的过程
实体类
@Data
public class BlackWhiteUserManage implements Serializable {
private Integer id;
...
/**
* 人脸图片
*/
private String faceImg;
/**
* 人脸图片文件名
*/
private String faceImgName;
...
}
查询SQL
select
id, ... , gender, age, face_img, face_img_name, ...
from
black_white_user_manage
...
操作符
(一)中间操作符
filter() 过滤
用于通过设置的条件过滤出元素(filter中判空是!=null)
一、String类型的字段进行非空过滤
// 查询这个结果集(后面代码段不再重复)
List<BlackWhiteUserManage> result = blackWhiteUserManageDao.queryAll(null);
// 针对 BlackWhiteUserManage 中的faceImg字段进行非空过滤
List<BlackWhiteUserManage> list = result.stream()
.filter(item -> item.getFaceImg() != null)
.collect(Collectors.toList());
二、非String类型的字段进行非空过滤
// 针对 BlackWhiteUserManage 中的gender字段进行非空过滤
List<BlackWhiteUserManage> list2 = result.stream()
.filter(item -> item.getGender() != null)
.collect(Collectors.toList());
三、同一字段进行多条件过滤
将需要过滤的条件进行拼接(用&&)
// 针对 age 字段进行了‘非空过滤’ 和 '年龄不少于20'的过滤
List<BlackWhiteUserManage> list4 = result.stream()
.filter(item -> item.getAge() !=null && item.getAge() > 20)
.collect(Collectors.toList());
四、不同字段组合过滤
将需要过滤的条件进行拼接(用&&)
List<BlackWhiteUserManage> list5 = result.stream()
.filter(item -> item.getAge() != null
&& item.getUserId() != null
&& item.getGender() != null
&& item.getFaceImg() != null
&& item.getAge() > 20)
.collect(Collectors.toList());
map() 映射
对流中的每个元素应用一个函数,将其映射为新的元素。这个函数会被应用到流中的每个元素,生成一个新的流,其中包含了应用映射函数后的结果。
一、输入年龄结果集
得到非空姓名set集
Set<Integer> listAgent = result.stream()
.filter(item -> item.getAge() != null)
.map(BlackWhiteUserManage::getAge) // 等价于 .map(item -> item.getAge())
.collect(Collectors.toSet());
注意:
1、map()方法强调原始流中的元素和新流中元素的个数要相同,或者理解为一一对应。map()方法是将流中的元素转换为另外一个流中的元素,转换前后两个流的元素个数不发生改变。
2、map()中不能像filter()一样写条件(如:item -> item.getAge() > 20),写了这样的条件后就会有问题,原始流为5条,这样的条件如果有效的话,得到的新流肯定与原始流不能一一对应。切记不要与去重弄混了,比如上面的代码得到结果集数是4,为什么又能执行呢?result中有5个元素,经过filter()后原始流中只有4个元素,map()后的新流依旧是4个元素(原始流与新流对应了),最后得到的结果实际上是经过set去重的,与map()无关。
distinct() 去重
返回的是由该流中不同元素组成的流。
一、String集合的去重
直接调用distinct()
// str 中的元素为 A A B B C
strList.forEach(str -> System.out.print(str));//去重前:AABBC
strList = strList.stream().distinct().collect(Collectors.toList());
System.out.println("");
System.out.print("去重后:");
strList.forEach(str -> System.out.print(str));//去重后:ABC
二、对象集合运用distinct()去重
就对象中某个属性或多个属性去重,就需要重写对象类的equals()和hashcode()方法。
实体类(重写了equals()和hashcode()方法)
如果UserInfo的实例对象obj1和obj2,obj1.userName.equals(obj2.userName)==true,就认为obj1 == obj2
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
private Integer userId;
private String userName;
private Integer age;
private Integer sex;
private String phone;
@Override
public boolean equals(Object obj) {
if (this == obj)//如果是同一个对象 直接返回true
return true;
if (obj == null)//如果比较的为null 直接返回false
return false;
if (getClass() != obj.getClass())//两者类类型不一样直接返回false
return false;
// 类型转换
UserInfo other = (UserInfo) obj;
if (userName == null) {
if (other.userName != null) return false;
} else if (!userName.equals(other.userName)) {
return false;
}
return true;
}
@Override
public int hashCode() {
// 31是经验验证的一个能够很好地减小哈希碰撞的质数
final int prime = 31;
int result = 1;
result = prime * result + ((userName == null) ? 0: userName.hashCode());
return result;
}
}
测试代码
UserInfo info1 = new UserInfo(1,"张三",18,1,"0731-2211");
UserInfo info2 = new UserInfo(1,"张三",18,1,"0731-2211");
UserInfo info3 = new UserInfo(3,"张三",18,1,"0731-2211");
UserInfo info4 = new UserInfo(4,"李四",18,1,"0731-2233");
UserInfo info5 = new UserInfo(5,"王五",18,1,"0731-2244");
List<UserInfo> result = new ArrayList<>();
result.add(info1);
result.add(info2);
result.add(info3);
result.add(info4);
result.add(info5);
List<UserInfo> userInfoList = result.stream().distinct().collect(Collectors.toList());
Iterator<UserInfo> iterator = userInfoList.iterator();
while (iterator.hasNext()) {
UserInfo userInfo = iterator.next();
System.out.println(userInfo);
}
结果:
UserInfo(userId=1, userName=张三, age=18, sex=1, phone=0731-2211),
UserInfo(userId=4, userName=李四, age=18, sex=1, phone=0731-2233),
UserInfo(userId=5, userName=王五, age=18, sex=1, phone=0731-2244)]
注意:
1、对象列表result(List<UserInfo>)中有5条记录,未重写equals()和hashcode()时,只能对完全一样的记录(info1、info2)去重
2、针对userName属性重写了equals()和hashcode()方法后,就对记录中userName出现重复的(info1、info2、info3)去重,结果显示,保留了info1。
3、对于对象列表,想要实现属性级的去重,必须要重写equals()和hashcode(),因为distinct()就是根据equals()处理的。上述例子是针对UserInfo中userName属性去重,实际上还可以多属性组合去重。
三、对象集合运用其它方法去重
(1)、使用Collectors两个静态方法collectingAndThen()和toCollection(),以及TreeSet<>来去重
// 根据userName属性去重(不能多属性组合去重)
List<BlackWhiteUserManage> list8 = result.stream()
.collect(Collectors.collectingAndThen(
Collectors.toCollection(
() -> new TreeSet<>(
Comparator.comparing(BlackWhiteUserManage::getUsername))), ArrayList::new));
简述Collectors 的操作:
聚合元素:toList、toSet、toCollection,这几个函数比较简单,是将聚合之后的元素,重新封装到队列中,然后返回。toList方法返回的是List子类,toSet返回的是Set子类,toCollection返回的是Collection子类。我们都知道,Collection的子类包括List、Set等众多子类,所以toCollection更加灵活。
toMap、toConcurrentMap,这两个方法的作用是将聚合元素,重新组装为Map结构,也就是 k-v 结构。toMap返回的是Map,toConcurrentMap返回ConcurrentMap。toMap()可玩性很高
操作链:collectingAndThen,先对集合进行一次聚合操作,然后通过Function定义的函数,对聚合后的结果再次处理。
(2)、巧用filter()+自定义函数
// 根据userName去重
List<BlackWhiteUserManage> list9 = result.stream()
.filter(distinctByKey(BlackWhiteUserManage::getUsername))
.collect(Collectors.toList());
//自定义函数
private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
Map<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
注意:对于重复元素,将保留在遇到顺序中排在最前面的元素(可能不是想要的)。
sorted() 排序
按自然升序对集合进行排序,默认是升序排列;使用 Comparator 提供 reverseOrder() 方法实现降序排列。
方法 | 说明 |
---|---|
sorted() | 自然排序(升序),流中元素需实现Comparable接口。 例:list.stream().sorted() |
sorted(Comparator com) | 定制排序。常用以下几种: list.stream().sorted(Comparator.reverseOrder()) //倒序排序(从大到小) list.stream().sorted(Comparator.comparing(Student::getAge)) //顺序排序(从小到大)list.stream().sorted(Comparator.comparing(Student::getAge).reversed()) // 倒序排序(从大到小) |
一、对象单字段(年龄从大到小)降序排序
List<BlackWhiteUserManage> list10 = result.stream()
.filter(item -> item.getAge() != null)
.sorted(Comparator.comparing(BlackWhiteUserManage::getAge).reversed())
.collect(Collectors.toList());
二、对象多字段全部升序排序
List<BlackWhiteUserManage> list11 = result.stream()
.filter(item -> item.getAge() != null && item.getUsername() != null)
.sorted(Comparator.comparing(BlackWhiteUserManage::getAge)
.thenComparing(BlackWhiteUserManage::getUsername))
.collect(Collectors.toList());
注意:
1、将age和username字段用于排序,这两个字段不能为null,否则代码直接报错
2、有一个comparing(),可以有多个thenComparing()。
3、在此例中实际上是按照comparing()中指定的age进行排序的,将comparing()指定为username时,就按照username进行排序。
limit
会返回一个不超过给定长度的流
// limit(3):取前3条数据
List<BlackWhiteUserManage> list13 = result.stream()
.limit(3).collect(Collectors.toList());
skip
返回一个扔掉了前n个元素的流
// skip(3):跳过第3条取后几条
List<BlackWhiteUserManage> list14 = result.stream()
.skip(3)
.collect(Collectors.toList());
flatMap
flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
List<String> list15 = result.stream()
.flatMap(obj -> Arrays.stream(obj.getUsername().split(",")))
.collect(Collectors.toList());
结果:[李四, 1, 老七, 老六, 老六]
map:对流中每一个元素进行处理
flatMap:流扁平化,让你把一个流中的“每个值”都换成另一个流,然后把所有的流连接起来成为一个流
本质区别:map是对一级元素进行操作,flatmap是对二级元素操作map返回一个值;flatmap返回一个流,多个值
应用场景:map对集合中每个元素加工,返回加工后结果;flatmap对集合中每个元素加工后,做扁平化处理后(拆分层级,放到同一层)然后返回
peek
对元素进行遍历并处理
List<BlackWhiteUserManage> list16 = result.stream()
.filter(item -> item.getUserId() != null)
.peek(item -> item.setUserId(item.getUserId() + 1))
.collect(Collectors.toList());
注意:只是对result结果集中的数据进行了二次处理,处理的字段必须不能为空(否则会报空指针异常)
(二)终端操作符
collect
收集器,将流转换为其他形式。具体有:toList、toSet和toCollection。
forEach
遍历流
// forEach:遍历当前流
result.stream().forEach(System.out::println);
结果:将流中的记录逐行答应出来。
findFirst
返回第一个元素
// findFirst:返回当前流中第一个元素
BlackWhiteUserManage blackWhiteUserManage1 = result.stream().findFirst().get();
结果:输出流中第一个元素
findAny
将返回当前流中的任意元素
// 返回当前流中任意元素
BlackWhiteUserManage blackWhiteUserManage2 = result.stream().findAny().get();
count
返回当前流中元素总数
// 返回当前流中元素的总数
long count = result.stream().count();
sum
求和,可以指定某个字段进行求和
// 求和
int sum = result.stream().mapToInt(item -> item.getDelFlag()).sum();
注意:指定的字段不能有空值(null),否则直接报空指针错误。可以用filter过滤掉
max
最大值,当前流的所有记录中某个字段的最大值
long max = result.stream()
.filter(item -> item.getUserId() != null)
.max(Comparator.comparingLong(item -> item.getUserId())).get().getUserId();
注意:指定的字段不能有控制(可以用filter先过滤掉),否则直接报空指针错误。
min
最小值,当前流的所有记录中某个字段的最小值
long min = result.stream()
.filter(item -> item.getUserId() != null)
.min(Comparator.comparingLong(item -> item.getUserId())).get().getUserId();
注意:指定的字段不能有控制(可以用filter先过滤掉),否则直接报空指针错误。
anyMatch
检查是否至少匹配一个元素,返回boolean
// anyMatch:检查是否至少匹配一个元素
boolean matchAny = result.stream().anyMatch(item ->"李四".equals(item.getUsername()));
有一个匹配就会返回true
allMatch
检查是否匹配所有元素,返回boolean
// allMatch:检查是否匹配所有元素
boolean matchAll = result.stream().allMatch(item ->"李四".equals(item.getUsername()));
流中所有数据项中字段全部匹配成功才会返回true
noneMatch
检查是否没有匹配所有元素,返回boolean
// noneMatch:检查是否没有匹配所有元素,返回boolean
boolean noneMatch = result.stream().noneMatch(item ->"王五".equals(item.getUsername()));
reduce
可以将流中元素反复结合起来,得到一个值
Optional reduce = result.stream().reduce((blackWhiteUserManage, blackWhiteUserManage2) -> {
return blackWhiteUserManage;
});
if (reduce.isPresent()) log.info("新的结果集"+reduce.get());
Collect收集
Collector:结果收集策略的核心接口,具备将指定元素累加存放到结果容器中的能力;并在Collectors工具中提供了Collector接口的实现类
toList
例:将用户名字放入一个List集合中
List<Long> userIdList = result.stream()
.map(item -> item.getUserId()).collect(Collectors.toList());
结果:[64, null, null, 62, 62]
toMap
将主键ID和Name以Key-Value形式存放到Map集合中
Map<Integer, String> userMap = result.stream()
.collect(Collectors.toMap(BlackWhiteUserManage::getId,BlackWhiteUserManage::getUsername));
结果:{2=李四, 3=1, 4=老七, 5=老六, 6=老六}
toSet
将用户的create_id放入到set集合中
Set<Integer> createdIds = result.stream()
.map(BlackWhiteUserManage::getCreateId).collect(Collectors.toSet());
结果:[65, 62]
counting
符合条件(年龄大于20)的用户总数
long count = result.stream()
.filter(item -> item.getAge() !=null && item.getAge() > 20)
.collect(Collectors.counting());
注意:测试数据的问题,这里加了一个age的非空过滤
summingInt
对结果元素(用户ID)求和
Integer sum = result.stream()
.filter(item -> item.getAge() != null && item.getAge() > 20)
.collect(Collectors.summingInt(BlackWhiteUserManage::getId));
结果:12
minBy
筛选元素中ID最小的用户
BlackWhiteUserManage minBy = result.stream()
.collect(Collectors.minBy(Comparator.comparingInt(BlackWhiteUserManage::getId))).get();
joining
将用户的名字,以指定分隔符链接成字符串;
String userNameStr = result.stream()
.map(BlackWhiteUserManage::getUsername).collect(Collectors.joining("||"));
结果:李四||1||老七||老六||老六
groupingBy
根据isLongEffective字段进行分组
Map<Integer, List<BlackWhiteUserManage>> map1 = result.stream()
.collect(Collectors.groupingBy(BlackWhiteUserManage::getIsLongEffective));
结果:
0=[BlackWhiteUserManage(…, isLongEffective=0,…),BlackWhiteUserManage(…, isLongEffective=0,…)],
1=[BlackWhiteUserManage(…, isLongEffective=1,…),…,BlackWhiteUserManage(…, isLongEffective=1,…)]
orElse(null)
表示如果一个都没找到返回null(orElse()中可以塞默认值。如果找不到就会返回orElse中设置的默认值)
orElseGet(null)
表示如果一个都没找到返回null(orElseGet()中可以塞默认值。如果找不到就会返回orElseGet中设置的默认值)