Java8新特性之Steam流详解

流的操作包含如下三个部分:创建流、中间流、关闭流,筛选去重映射排序属于流的中间操作,收集属于终止操作。Stream是流操作的基础关键类。

一、创建流

(1)通过集合创建流

// 通过集合创建流
List lists = new ArrayList<>();
lists.stream();

(2)通过数组创建流

// 通过数组创建流
String[] strings = new String[5];
Stream.of(strings);

二、日常操作

(1)筛选

筛选是指从(集合)流中筛选满足条件的子集,通过 Lambda 表达式生产型接口来实现。

// 通过断言型接口实现元素的过滤
stream.filter(x->x.getId()>10);

非空过滤

非空过滤包含两层内容:

一是当前对象是否为空或者非空;

二是当前对象的某属性是否为空或者非空。

筛选非空对象,语法stream.filter(Objects::nonNull)做非空断言。

// 非空断言
java.util.function.Predicate nonNull = Objects::nonNull;

(2)去重

去重是指将(集合)流中重复的元素去除,通过 hashcode 和 equals 函数来判断是否是重复元素。去重操作实现了类似于 HashSet 的运算,对于对象元素流去重,需要重写 hashcode 和 equals 方法。

如果流中泛型对象使用 Lombok 插件,使用@Data注解默认重写了 hashcode 和 equals 方法,字段相同并且属性相同,则对象相等。

stream.distinct();

(3)映射

取出流中元素的某一列,然后配合收集以形成新的集合。

stream.map(x->x.getId());

filtermap操作通常结合使用,取出流中某行某列的数据,建议先行后列的方式定位。

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);
}

(4)排序

传统的Collectors类中的排序支持 List 实现类中的一部分排序,使用 stream 排序,能够覆盖所有的 List 实现类。

// 按照默认字典顺序排序
stream.sorted();
// 按照sortNo排序
stream.sorted((x,y)->Integer.compare(x.getSortNo(),y.getSortNo()));

1、函数式接口排序

基于 Comparator 类中函数式方法,能够更加优雅的实现对象流的排序。

// 正向排序(默认)
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getId));
// 逆向排序
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getId).reversed());

2、LocalDate 和 LocalDateTime 排序

新日期接口相比就接口,使用体验更加,因此越来越多的被应用,基于日期排序是常见的操作。

// 准备测试数据
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)将流中的中间(计算)结果存储到集合中,方便后续进一步使用。

1、普通收集

(1)收集为List

默认返回的类型为ArrayList,可通过Collectors.toCollection(LinkedList::new)显示指明使用其它数据结构作为返回值容器。

List collect = stream.collect(Collectors.toList());

由集合创建流的收集需注意:仅仅修改流字段中的内容,没有返回新类型,如下操作直接修改原始集合,无需处理返回值。

// 直接修改原始集合
userVos.stream().map(e -> e.setDeptName(hashMap.get(e.getDeptId())))
.collect(Collectors.toList());

(2)收集为Set

默认返回类型为HashSet,可通过Collectors.toCollection(TreeSet::new)显示指明使用其它数据结构作为返回值容器。

Set collect = stream.collect(Collectors.toSet());

2、高级收集

(1)收集为Map

默认返回类型为HashMap,可通过Collectors.toCollection(LinkedHashMap::new)显示指明使用其它数据结构作为返回值容器。

收集为Map的应用场景更为强大,下面对这个场景进行详细介绍。希望返回结果中能够建立IDNAME之间的匹配关系,最常见的场景是通过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));

IDObject类映射

@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 keyMapper,
                                    
Function valueMapper) 
{
    return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

(2)分组收集

流的分组收集操作在内存层次模拟了数据库层面的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())));

四、Steam拓展

(一)集合与对象互转

将对象包装成集合的形式和将集合拆解为对象的形式是常见的操作。

1、对象转集合

/**
 * 将单个对象转化为集合
 *
 * @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));
}

2、集合转对象

使用默认的排序规则,注意此处不是指自然顺序排序。

/**
 * 取出集合中第一个元素
 *
 * @param collection 集合实例
 * @param         集合中元素类型
 * @return 泛型类型
 */
public static  E toObject(Collection collection) {
    // 处理集合空指针异常
    Collection coll = Optional.ofNullable(collection).orElseGet(ArrayList::new);
    // 此处可以对流进行排序,然后取出第一个元素
    return coll.stream().findFirst().orElse(null);
}

上述方法巧妙的解决两个方面的异常问题:

一是集合实例引用空指针异常;

二是集合下标越界异常。

(二)其他

1、并行计算

基于流式计算中的并行流,能够显著提高大数据下的计算效率,充分利用 CPU 核心数。

// 通过并行流实现数据累加
LongStream.rangeClosed(1,9999999999999999L).parallel().reduce(0,Long::sum);

2、序列数组

生成指定序列的数组或者集合。

// 方式一:生成数组
int[] ints = IntStream.rangeClosed(1, 100).toArray();
// 方式二:生成集合
List list = Arrays.stream(ints).boxed().collect(Collectors.toList());

六、流的应用Demo

列表转树

        传统方式下构建树形列表需要反复递归调用查询数据库,效率偏低。对于一棵结点较多的树,效率更低。这里提供一种只需调用一次数据库,通过流将列表转化为树的解决方式。

/**
 * 列表转树
 *
 * @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;
}

你可能感兴趣的:(java基础,java,开发语言)