流的操作包含如下三个部分:创建流、中间流、关闭流,筛选
、去重
、映射
、排序
属于流的中间操作,收集
属于终止操作。Stream是流操作的基础关键类。
// 通过集合创建流
List lists = new ArrayList<>();
lists.stream();
// 通过数组创建流
String[] strings = new String[5];
Stream.of(strings);
筛选是指从(集合)流中筛选满足条件的子集,通过 Lambda 表达式生产型接口来实现。
// 通过断言型接口实现元素的过滤
stream.filter(x->x.getId()>10);
非空过滤
非空过滤包含两层内容:
一是当前对象是否为空或者非空;
二是当前对象的某属性是否为空或者非空。
筛选非空对象,语法stream.filter(Objects::nonNull)
做非空断言。
// 非空断言
java.util.function.Predicate nonNull = Objects::nonNull;
去重是指将(集合)流中重复的元素去除,通过 hashcode 和 equals 函数来判断是否是重复元素。去重操作实现了类似于 HashSet 的运算,对于对象元素流去重,需要重写 hashcode 和 equals 方法。
如果流中泛型对象使用 Lombok 插件,使用@Data
注解默认重写了 hashcode 和 equals 方法,字段相同并且属性相同,则对象相等。
stream.distinct();
取出流中元素的某一列,然后配合收集以形成新的集合。
stream.map(x->x.getId());
filter和map操作通常结合使用,取出流中某行某列的数据,建议先行后列的方式定位。
Optional model = data.stream().filter(e -> e.getId().equals(Id)).findFirst();
if (model.isPresent()) {
String itemName = model.get().getItemName();
String itemType = model.get().getItemType();
return new MainExportVo(itemId, itemName);
}
传统的Collectors
类中的排序支持 List 实现类中的一部分排序,使用 stream 排序,能够覆盖所有的 List 实现类。
// 按照默认字典顺序排序
stream.sorted();
// 按照sortNo排序
stream.sorted((x,y)->Integer.compare(x.getSortNo(),y.getSortNo()));
基于 Comparator 类中函数式方法,能够更加优雅的实现对象流的排序。
// 正向排序(默认)
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getId));
// 逆向排序
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getId).reversed());
新日期接口相比就接口,使用体验更加,因此越来越多的被应用,基于日期排序是常见的操作。
// 准备测试数据
Stream stream = Stream.of(new DateModel(LocalDate.of(2020, 1, 1))
, new DateModel(LocalDate.of(2021, 1, 1)), new DateModel(LocalDate.of(2022, 1, 1)));
正序、逆序排序
// 正向排序(默认)
stream.sorted(Comparator.comparing(DateModel::getLocalDate))
.forEach(System.out::println);
// 逆向排序
stream.sorted(Comparator.comparing(DateModel::getLocalDate).reversed())
.forEach(System.out::println);
(5)规约(reduce)
对流中的元素按照一定的策略计算。终止操作的底层逻辑都是由 reduce 实现的。
收集(collect)将流中的中间(计算)结果存储到集合中,方便后续进一步使用。
默认返回的类型为ArrayList
,可通过Collectors.toCollection(LinkedList::new)
显示指明使用其它数据结构作为返回值容器。
List collect = stream.collect(Collectors.toList());
由集合创建流的收集需注意:仅仅修改流字段中的内容,没有返回新类型,如下操作直接修改原始集合,无需处理返回值。
// 直接修改原始集合
userVos.stream().map(e -> e.setDeptName(hashMap.get(e.getDeptId())))
.collect(Collectors.toList());
默认返回类型为HashSet
,可通过Collectors.toCollection(TreeSet::new)
显示指明使用其它数据结构作为返回值容器。
Set collect = stream.collect(Collectors.toSet());
默认返回类型为HashMap
,可通过Collectors.toCollection(LinkedHashMap::new)
显示指明使用其它数据结构作为返回值容器。
收集为Map
的应用场景更为强大,下面对这个场景进行详细介绍。希望返回结果中能够建立ID
与NAME
之间的匹配关系,最常见的场景是通过ID
批量到数据库查询NAME
,返回后再将原数据集中的ID
替换成NAME
。
ID 到 NAME 映射
@Data
public class Entity {
private Integer id;
private String name;
}
准备集合数据,此部分通常是从数据库查询的数据
// 模拟从数据库中查询批量的数据
List entityList = Stream.of(new Entity(1,"A"),
new Entity(2,"B"), new Entity(3,"C")).collect(Collectors.toList());
将集合数据转化成 ID 与 NAME 的 Map
// 将集合数据转化成ID与NAME的Map
Map hashMap = entityList.stream()
.collect(Collectors.toMap(Entity::getId, Entity::getName));
ID
与Object
类映射
@Data
public class Entity {
private Integer id;
private String name;
private Boolean status;
}
将集合数据转化成 ID 与实体类的 Map
// 将集合数据转化成ID与实体类的Map
Map hashMap =
entityList.stream().collect(Collectors.toMap(ItemEntity::getItemId, e -> e));
其中Collectors
类中的toMap
参数是函数式接口参数,能够自定义返回值。
public static Collector> toMap
(Function super T, ? extends K> keyMapper,
Function super T, ? extends U> valueMapper)
{
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
流的分组收集操作在内存层次模拟了数据库层面的group by
操作,下面演示流的分组操作。Collectors类提供了各种层次的分组操作支撑。
流的分组能力对应数据库中的聚合函数,目前大部分能在数据库中操作的聚合函数,都能在流中找到相应的能力。
// 默认使用List作为分组后承载容器
Map> hashMap =
xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId));
// 显示指明使用List作为分组后承载容器
Map> hashMap =
xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId, Collectors.toList()));
映射后再分组
Map> hashMap =
xUsers.stream().collect(Collectors.groupingBy
(XUser::getDeptId,Collectors.mapping(XUser::getUserName,Collectors.toList())));
将对象包装成集合的形式和将集合拆解为对象的形式是常见的操作。
/**
* 将单个对象转化为集合
*
* @param t 对象实例
* @param 对象类型
* @param 集合类型
* @return 包含对象的集合实例
*/
public static > Collection toCollection(T t) {
return toCollection(t, ArrayList::new);
}
用户自定义返回的集合实例类型
/**
* 将单个对象转化为集合
*
* @param t 对象实例
* @param supplier 集合工厂
* @param 对象类型
* @param 集合类型
* @return 包含对象的集合实例
*/
public static > Collection toCollection(T t, Supplier supplier) {
return Stream.of(t).collect(Collectors.toCollection(supplier));
}
使用默认的排序规则,注意此处不是指自然顺序排序。
/**
* 取出集合中第一个元素
*
* @param collection 集合实例
* @param 集合中元素类型
* @return 泛型类型
*/
public static E toObject(Collection collection) {
// 处理集合空指针异常
Collection coll = Optional.ofNullable(collection).orElseGet(ArrayList::new);
// 此处可以对流进行排序,然后取出第一个元素
return coll.stream().findFirst().orElse(null);
}
上述方法巧妙的解决两个方面的异常问题:
一是集合实例引用空指针异常;
二是集合下标越界异常。
基于流式计算中的并行流,能够显著提高大数据下的计算效率,充分利用 CPU 核心数。
// 通过并行流实现数据累加
LongStream.rangeClosed(1,9999999999999999L).parallel().reduce(0,Long::sum);
生成指定序列的数组或者集合。
// 方式一:生成数组
int[] ints = IntStream.rangeClosed(1, 100).toArray();
// 方式二:生成集合
List list = Arrays.stream(ints).boxed().collect(Collectors.toList());
列表转树
传统方式下构建树形列表需要反复递归调用查询数据库,效率偏低。对于一棵结点较多的树,效率更低。这里提供一种只需调用一次数据库,通过流将列表转化为树的解决方式。
/**
* 列表转树
*
* @param rootList 列表的全部数据集
* @param parentId 第一级目录的父ID
* @return 树形列表
*/
public List getChildNode(List rootList, String parentId) {
List lists = rootList.stream()
.filter(e -> e.getParentId().equals(parentId))
.map(IndustryNode::new).collect(toList());
lists.forEach(e -> e.setChilds(getChildNode(rootList, e.getId())));
return lists;
}