Java Stream 增强库。在标准 Stream 之上补充常用 API,减少样板代码,对空值更友好。
<dependency>
<groupId>io.github.taowater</groupId>
<artifactId>ztream</artifactId>
<version>LATEST</version>
</dependency>List<String> names = Ztream.of(users)
.nonNull()
.eq(User::getStatus, 1)
.asc(User::getAge)
.toList(User::getName);Java 8 Stream 让集合处理更清晰,但业务开发里仍常遇到:
- 必要 API 缺失(按属性去重、带索引遍历等)
groupingBy/toMap等写法冗长,样板代码多- 标准分组、映射对空 key / 空 value 不够友好
高版本 JDK 已做了部分改善,JDK 22 还引入了 Gatherer。即便如此,Ztream 中一批常用 API 在 Java 8 及日常 CRUD 场景里仍然很实用。
本库本质是对 Stream 的封装与增强,追求更好写、更好读。
对流操作的一些库,在我冲浪参考的过程中,还有这些:
倘拙库可用,用之;如其不才,君可自取。
// 空流
Ztream<String> empty = Ztream.empty();
// 可变参数
Ztream<Integer> nums = Ztream.of(1, 2, 3);
// Collection / Iterable
Ztream<Student> students = Ztream.of(list);
Ztream<Student> parallel = Ztream.of(list, true);
// 标准 Stream
Ztream<Student> fromStream = Ztream.of(list.stream());
// Map → EntryZtream
EntryZtream<String, Integer> entries = Ztream.of(map);
// 数字范围 [start, end)
Ztream<Integer> range = Ztream.range(0, 10);
// 数字范围 [start, end]
Ztream<Integer> closed = Ztream.range(0, 10, true);
Ztream<Long> longRange = Ztream.range(0L, 100L);
// 按分隔符拆分字符串
Ztream<String> parts = Ztream.split("a,b,c");
Ztream<Integer> ids = Ztream.split("1,2,3", ",", Integer::valueOf);// Stream
List<String> names = Stream.of(users).map(User::getName).collect(Collectors.toList());
// Ztream(Java 16+ 标准 Stream 已有 toList)
List<String> names = Ztream.of(users).map(User::getName).toList();
// 直接收集属性
List<String> names = Ztream.of(users).collect(User::getName);
List<String> names = Ztream.of(users).toList(User::getName);
// 收集为 Set
Set<String> nameSet = Ztream.of(users).toSet(User::getName);
// 指定集合类型
Set<String> treeNames = Ztream.of(users).collect(User::getName, TreeSet::new);
List<User> linked = Ztream.of(users).collect(LinkedList::new);
// 转换类型后收集
List<Teacher> teachers = Ztream.of(users).toList(Teacher.class);风格接近 MyBatis-Plus:属性比较、空值判断、条件拼接、and / or 组合。
List<Student> result = Ztream.of(list)
.eq(Student::getAge, 34) // 等于
.gt(Student::getAge, 18) // 大于
.ge(Student::getScore, 60) // 大于等于
.lt(Student::getScore, 100) // 小于
.le(Student::getAge, 60) // 小于等于
.between(Student::getAge, 18, 60) // 区间(含边界)
.in(Student::getName, "刘备", "关羽")
.notIn(Student::getName, "张飞")
.toList();Ztream.of(list)
.nonNull() // 元素非 null
.nonNull(Student::getName) // 属性非 null
.isNull(Student::getRemark)
.nonEmpty(Student::getName)
.nonBlank(Student::getName)
.isBlank(Student::getName)
.rightLike(Student::getName, "小") // 前缀匹配 startsWith
.like(Student::getName, "猪") // 模糊匹配
.isTrue(Student::getFlag)
.isFalse(Student::getFlag);多数过滤方法支持首参 boolean condition:条件为假时跳过该过滤。
boolean checkScore = true;
List<Student> result = Ztream.of(list)
.eq(Student::getAge, 34)
.like(Student::getName, "朱")
.ge(Student::getScore, 60)
.lt(checkScore, Student::getScore, 80) // 仅当 checkScore 为 true 时生效
.toList();通过 query 进入条件包装器,支持 and / or 组合:
List<Student> result = Ztream.of(list)
.query(w -> w
.eq(Student::getAge, 2)
.in(Student::getName, "刘备", "关羽")
.or(w2 -> w2
.like(Student::getName, "刘")
.gt(Student::getScore, 90)
)
.and(w2 -> w2
.ge(Student::getAge, 18)
)
)
.toList();简单条件也可直接在流上链式调用(均为 and 关系):
Ztream.of(list)
.eq(Student::getAge, 34)
.like(Student::getName, "朱")
.ge(Student::getScore, 60)
.toList();// 普通 map
Ztream.of(list).map(Student::getName);
// 带索引的 map
Ztream.of(list).map((e, i) -> i + ":" + e.getName());
// 同时映射多个属性并展开为一个流
Ztream.of(list).map(Student::getName, Student::getSex);
// 展开集合属性(flatMap 简写)
Ztream.of(orders).flat(Order::getItems);
// 类型强转
Ztream.of(list).cast(Student.class);
// 类型转换(基于 ConvertUtil)
Ztream.of(list).convert(Teacher.class);
Ztream.of(list).convert(Teacher.class, (src, dest) -> {
// 可对转换后的对象做额外赋值
});// Stream:key 或 value 为 null 时常抛异常
Map<String, Integer> map = Stream.of(list)
.collect(Collectors.toMap(Student::getName, Student::getAge));
// Ztream:允许 null
Map<String, Integer> map = Ztream.of(list).toMap(Student::getName, Student::getAge);
// 仅指定 key,value 为元素本身
Map<String, Student> studentMap = Ztream.of(list).toMap(Student::getName);
// 指定 Map 实现
Map<String, Integer> linked = Ztream.of(list)
.toMap(Student::getName, Student::getAge, LinkedHashMap::new);重复 key 时保留先出现的值(不抛异常)。
// Stream
Map<String, List<Student>> group = Stream.of(list)
.collect(Collectors.groupingBy(Student::getName));
// Ztream
Map<String, List<Student>> group = Ztream.of(list).groupBy(Student::getName);
// 分组并映射值
Map<String, List<Integer>> ages = Ztream.of(list)
.groupBy(Student::getName, Student::getAge);
// 指定 Map / 下游收集器
Map<String, Set<Integer>> ageSets = Ztream.of(list)
.groupBy(Student::getName, Student::getAge, HashMap::new, Collectors.toSet());
// 两层分组
Map<String, Map<String, List<Student>>> bilayer =
Ztream.of(list).groupBilayer(Student::getSex, Student::getClassName);// hash:按 key 去重后转为键值对流
Ztream.of(list)
.hash(Student::getName, Student::getAge)
.flip()
.forEachKeyValue((age, name) -> {
// doSomething
});
// group:分组中间操作,继续链式处理
Ztream.of(list)
.group(Student::getName, Student::getAge, Collectors.toSet())
.filterKey(k -> k != null)
.toMap();由 Map、hash、group 得到,面向 key / value 操作。
EntryZtream<String, Integer> stream = Ztream.of(map);
// 取出 key / value 流
Ztream<String> keys = stream.keys();
Ztream<Integer> values = stream.values();
// 过滤
stream.nonNullKey()
.nonNullValue()
.filterKey(k -> k.startsWith("小"))
.filterValue(v -> v > 18)
.filter((k, v) -> k.length() > 1);
// 映射
stream.mapKey(String::toUpperCase)
.mapValue(v -> v + 1)
.mapKeyValue((k, v) -> k + "=" + v);
// 键值互换
EntryZtream<Integer, String> flipped = stream.flip();
// 遍历
stream.forEachKeyValue((k, v) -> { /* ... */ });
stream.peekKeyValue((k, v) -> { /* ... */ });
// 收集回 Map
Map<String, Integer> result = stream.toMap();
Map<String, Integer> linked = stream.toMap(LinkedHashMap::new);// 按属性升序 / 降序
List<Student> asc = Ztream.of(list).asc(Student::getAge).toList();
List<Student> desc = Ztream.of(list).desc(Student::getAge).toList();
// 元素自身可比时
Ztream.of(1, 3, 2).asc().toList(); // [1, 2, 3]
Ztream.of(1, 3, 2).desc().toList(); // [3, 2, 1]
// 多字段排序:先按年龄降序(null 靠前),年龄相同按姓名升序(null 靠后)
List<Student> sorted = Ztream.of(list)
.sort(r -> r
.desc(flag, Student::getAge, true) // flag 为 true 时才应用
.asc(Student::getName, false)
.nullFirst(false) // 控制元素本身为 null 时的位置
)
.toList();
// 洗牌 / 反转
Ztream.of(list).shuffle().toList();
Ztream.of(list).reverse().toList();// 标准去重
Ztream.of(1, 2, 2, 3).distinct().toList(); // [1, 2, 3]
// 按属性去重(保留首次出现)
Ztream.of(list).distinct(Student::getName).toList();
// 条件去重
Ztream.of(list).distinct(needDistinct, Student::getName);// 默认逗号分隔 → "刘备,关羽,张飞"
String str = Ztream.of(list).join(Student::getName);
// 指定分隔符 → "刘备#关羽#张飞"
String str = Ztream.of(list).join(Student::getName, "#");
// 元素本身拼接
String str = Ztream.of("a", "b", "c").join(); // "a,b,c"
String str = Ztream.of("a", "b", "c").join("#"); // "a#b#c"
String str = Ztream.of("a", "b", "c").join("#", "[", "]"); // "[a#b#c]"属性需为 Number / Comparable 类型。
// 最值(属性值)
Integer maxAge = Ztream.of(list).max(Student::getAge);
Integer minAge = Ztream.of(list).min(Student::getAge);
Integer maxAge = Ztream.of(list).max(Student::getAge, 0); // 默认值
// 求和 / 平均
Integer sum = Ztream.of(list).sum(Student::getAge);
Integer sum = Ztream.of(list).sum(Student::getAge, 0);
Integer avg = Ztream.of(list).avg(Student::getAge, 0);
// 取最值对应的元素(返回 Any)
Student oldest = Ztream.of(list).maxBy(Student::getAge).orElse(null);
Student youngest = Ztream.of(list).minBy(Student::getAge, false).orElse(null);
// 元素自身可比时
Any<Integer> max = Ztream.of(1, 2, 3, null).max();
Any<Integer> min = Ztream.of(1, 2, 3, null).min(false); // null 不视为最小// forEach:元素 + 下标
Ztream.of(list).forEach((e, i) -> {
System.out.println(i + " -> " + e);
});
// peek:中间操作
Ztream.of(list)
.peek((e, i) -> System.out.println(i))
.map(Student::getName)
.toList();
// map:带索引映射
Ztream.of(list).map((e, i) -> new Ranked(i, e)).toList();
// 查找第一个满足条件的下标(未找到返回 -1)
int idx = Ztream.of(list).firstIdx(e -> "刘备".equals(e.getName()));// 是否存在重复元素 / 重复属性
boolean repeat = Ztream.of(list).hadRepeat();
boolean nameRepeat = Ztream.of(list).hadRepeat(Student::getName);
// 按属性匹配
boolean anyNull = Ztream.of(list).anyMatch(Student::getName, Objects::isNull);
boolean allNull = Ztream.of(list).allMatch(Student::getName, Objects::isNull);
boolean noneNull = Ztream.of(list).noneMatch(Student::getName, Objects::isNull);
// 是否为空流
boolean empty = Ztream.of(list).nonNull().isEmpty();
boolean notEmpty = Ztream.of(list).isNotEmpty();// 追加元素
Ztream.of(1, 2, 3).append(4, 5, 6).toList();
Ztream.of(list).append(otherList).toList();
// 分页(页码从 1 开始)
List<Student> page = Ztream.of(list).page(2, 10).toList();
// 取首 / 尾 / 随机(返回 Any,便于继续链式取值)
Any<Student> first = Ztream.of(list).first();
Student firstOne = Ztream.of(list).getFirst();
Any<Student> last = Ztream.of(list).last();
Student lastOne = Ztream.of(list).getLast();
Any<Student> random = Ztream.of(list).random();
// findFirst / findAny:可选择是否因 null 抛 NPE
Optional<Student> opt = Ztream.of(list).findFirst(false);更多方法可直接 Ztream.of(obj). 点出来探索。
