Stream API 主要操作:
筛选与切片
filter(Predicate) 筛选元素,从流中排除不满足Predicate的某些元素
limit(n) 截断流,使其元素不超过给定数量
skip(n) 跳过前面n个元素,若元素不足n个,则返回空流
distinct() 去重,通过流所生成元素的hashCode()与equals()去除重复元素
实体类
public class User {
private String name; // 姓名
private String age; // 年龄
private double salary; // 薪水
private Status status; // 工作状态:FREE 空闲, BUSY 繁忙 INVOCATION 休假
public enum Status {
BUSY, FREE, INVOCATION;
}
public User(){
}
public User(String name, String age, double salary, Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
// Getter Setter equals hashCode toString
}
List<User> list = Arrays.asList(
new User("张三", 21, 6000, User.Status.BUSY),
new User("李四", 23, 7000, User.Status.FREE),
new User("王五", 25, 6000, User.Status.INVOCATION),
new User("赵六", 21, 10000, User.Status.BUSY),
new User("赵六", 21, 10000, User.Status.BUSY)
);
// filter 筛选出BUSY状态的员工
list.stream()
.filter(u -> User.Status.BUSY.equals(u.getStatus()))
.forEach(System.out::println);
/* 结果:
User{name='张三', age='21', salary=6000.0, status=BUSY}
User{name='赵六', age='21', salary=10000.0, status=BUSY}
User{name='赵六', age='21', salary=10000.0, status=BUSY}
*/
// 筛选出工资等于6000的一个员工
list.stream()
.filter(u -> u.getSalary() == 6000)
.limit(1)
.forEach(System.out::println);
/* 结果:
User{name='张三', age='21', salary=6000.0, status=BUSY}
*/
// 筛选工资等于6000,不是第一个员工的员工
list.stream()
.filter(u -> u.getSalary() == 6000)
.skip(1)
.forEach(System.out::println);
/* 结果:
User{name='王五', age='25', salary=6000.0, status=INVOCATION}
*/
// 员工列表去重
list.stream()
.distinct()
.forEach(System.out::println);
/* 结果:
User{name='张三', age='21', salary=6000.0, status=BUSY}
User{name='李四', age='23', salary=7000.0, status=FREE}
User{name='王五', age='25', salary=6000.0, status=INVOCATION}
User{name='赵六', age='21', salary=10000.0, status=BUSY}
*/
映射
map(Function<T, R>) 提取元素,使用Function应用到每个元素
flatMap(Function<T, Stream<R>>) 提取元素,应用每个元素得到每一个新的Stream,然后将这些Stream合并成新的Stream
public static void test() {
List<String> list = Arrays.asList("abc", "def", "ghi");
// 将每个元素(这里指的每个元素是指"abc", "def"这种),转换成大写
list.stream()
.map(String::toUpperCase)
.forEach(System.out::print);
/* 结果:
ABCDEFGHI
*/
// 提取所有字符
// 步骤:应用每个元素,比如"abc"将其转换成Stream<Charactor>,然后得到三个Stream<Charactor>,最终合并到一个新的Stream<Character>,类似于list.addAll
list.stream()
.flatMap(Test::strToCharator) // ==> .flatMap(s -> strToCharator(s))
.forEach(System.out::print);
/* 结果:
abcdefghi
*/
}
public static Stream<Character> strToCharator(String s){
List<Character> list = new ArrayList<>();
for (char c : s.toCharArray()) {
list.add(c);
}
return list.stream();
}
重点说下区别:
Stream<Stream<Character>> characterStream = list.stream()
.map(Test::strToCharator);
characterStream.forEach(s -> s.forEach(System.out::println));
// s = Stream<Character> ,所有需要再迭代遍历一次,共两次
// 变化为 "abc","bcd", "ghi" --> {a, b, c}, {b, c, d}, {g, h , i} --> { {a, b, c}, {b, c, d}, {g, h , i} }
Stream<Character> characterStream2 = list.stream()
.flatMap(Test::strToCharator);
characterStream2.forEach(System.out::println);
// 变化为 "abc","bcd", "ghi" --> {a, b, c }, {b, c, d}, {g, h , i} --> {a,b,c,d,e,f,g,h,i}
类比于List list1, List list2
list1.add(list2) // 将list2作为list1的元素
list1.addAll(list2) // 将list2中所有元素放入到list1中
排序
sorted() 自然排序
sorted(Comparator) 自定义排序
List<String> list = Arrays.asList("bcd", "abe", "afa");
// 自然排序
list.stream()
.sorted()
.forEach(System.out::println);
// 按照string排序
list.stream()
.sorted(String::compareTo)
.forEach(System.out::println);
// 按照第二个元素排序
list.stream()
.sorted((s1, s2) -> Character.valueOf(s1.charAt(1)).compareTo( Character.valueOf(s2.charAt(1))))
.forEach(System.out::println);
// 等于上面,使用工具类 Comparator 更加方便
list.stream()
.sorted(Comparator.comparing(s -> s.charAt(1)))
.forEach(System.out::println);
查找与匹配
allMatch 流中所有元素都匹配成功,返回true
anyMatch 流中匹配一个元素即返回true
noneMatch 流中所有元素都不匹配,返回true,与allMatch相反
findFirst 返回流中匹配的第一个元素
findAny 返回流中匹配到的第一个元素,由于可能是parallelStream并行方式,所以不等于findFirst
count 返回流中元素个数
max 返回流中最大值
min 返回流中最小值
List<User> list = Arrays.asList(
new User("张三", 21, 6000, User.Status.BUSY),
new User("李四", 23, 7000, User.Status.FREE),
new User("王五", 25, 6000, User.Status.INVOCATION),
new User("赵六", 21, 10000, User.Status.BUSY)
);
// 1. 所有员工是否都处于BUSY状态
boolean b = list.stream().allMatch(u -> User.Status.BUSY.equals(u.getStatus())); // ==> false
// 更好的方式: list.stream().map(User::getStatus).allMatch(User.Status.BUSY::equals)
// 2. 至少有一个员工处理BUSY状态
boolean b2 = list.stream().map(User::getStatus).anyMatch(User.Status.BUSY::equals); // ==> true
// 3. 所有员工都不处于BUSY状态
boolean b3 = list.stream().map(User::getStatus).noneMatch(User.Status.BUSY::equals); // ==> false
// 4. 找出处于BUSY状态的第一个员工
Optional<User> firstUser = list.stream().filter(u -> User.Status.BUSY.equals(u.getStatus())).findFirst(); // firstUser.get() 张三
// 5. 找出任意一个处于BUSY状态的员工
Optional<User> anyUser = list.parallelStream().filter(u -> User.Status.BUSY.equals(u.getStatus())).findAny(); // anyUser.get() 赵六
// 6. 处于BUSY状态的员工数量
long count = list.stream().map(User::getStatus).filter(User.Status.BUSY::equals).count(); // ==> 2
// 7. 找到工资最高的员工
Optional<User> maxSalaryUser = list.stream().max(Comparator.comparingDouble(User::getSalary)); // maxSalaryUser.get() 赵六
// 8. 找到年龄最小的员工
Optional<User> minAgeUser = list.stream().min(Comparator.comparingInt(User::getAge)); // minAgeUser.get() 张三
归约与收集
reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中的元素反复结合起来,得到一个值
collect 将流转换成其他形式,接收
BinaryOperator extends BiFunction 所以是 T, T, R, 两个参数,一个返回值
reduce
List<User> list = Arrays.asList(
new User("张三", 21, 6000, User.Status.BUSY),
new User("李四", 23, 7000, User.Status.FREE),
new User("王五", 25, 6000, User.Status.INVOCATION),
new User("赵六", 21, 10000, User.Status.BUSY)
);
int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 1. 数组所有元素求和
int sum = Arrays.stream(arr).reduce(0, (x, y) -> x + y); // 55
// 说明:这里identity=0,指代第一次运算的x=0,y=数组第一个元素,也就是1,如果identity=2,那么第一次就是x=2, y=1, 那么最终结果=56
// 由于有第一次运算默认值,所以这里一定有返回值,所以设计为返回int,假设没有初始值,也就是下面这种,不确定元素是否足够,返回Optional
OptionalInt optionalInt = Arrays.stream(arr).reduce((x, y) -> x + y); // optionalInt.getAsInt() == 55
// OptionalInt 为 Optional 专门为int的包装类,比Optional<Integer>性能更好,减少装箱拆箱操作
// 2. 计算所有员工工资总和
Optional<Double> sumSalary = list.stream().map(User::getSalary).reduce(Double::sum); // sumSalary.get()
map-reduce 这种思想应用很广,比如hadoop,提取然后计算
collect
List<User> list = Arrays.asList(
new User("张三", 21, 6000, User.Status.BUSY),
new User("李四", 23, 7000, User.Status.FREE),
new User("王五", 25, 6000, User.Status.INVOCATION),
new User("赵六", 21, 10000, User.Status.BUSY)
);
// ---------------------- 转换为集合 -------------------
// collect(Collector),可以工具类Collectors生成Collector
// 1. 获取员工姓名列表
// 返回一个list
List<String> userNameList = list.stream().map(User::getName).collect(Collectors.toList());
// 返回set
Set<String> userNameSet = list.stream().map(User::getName).collect(Collectors.toSet());
// 返回hashSet
HashSet<String> userNameHashSet = list.stream().map(User::getName).collect(Collectors.toCollection(HashSet::new));
// ---------------------- 计算 ------------------------
// 2. 得到员工数量
// 总数
Long count = list.stream().collect(Collectors.counting()); // == list.stream().count()
// 工资大于5000员工数量
Long salaryConditionCount = list.stream().map(User::getSalary).filter(s -> s > 5000).collect(Collectors.counting()); // == filter().count()
// 3. 所有员工平均工资
Double averagSalary = list.stream().collect(Collectors.averagingDouble(User::getSalary));
// 4. 最高/最低工资
Optional<Double> maxSalary = list.stream().map(User::getSalary).collect(Collectors.maxBy(Double::compare)); // == map().max()
// -------------------- 分组 --------------------------
// 类似于SQL中的group by
// 5. 根据工作状态分组 (单级分组) { BUSY: List<User>, FREE: List<User> }
Map<User.Status, List<User>> userMapByStatus = list.stream().collect(Collectors.groupingBy(User::getStatus));
// 6. 先根据工作状态分组,再根据年龄分组 (多级分组){ BUSY: {21 : List<User>, 23 : List<User>}, FREE : { 25 : List<User>, 26 : List<User> } }
Map<User.Status, Map<Integer, List<User>>> userMapByStatusAndAge = list.stream()
.collect(Collectors.groupingBy(User::getStatus, Collectors.groupingBy(User::getAge)));
// 7. 更多级分组
Map<User.Status, Map<Integer, Map<Double, List<User>>>> userMapByStatusAndAgeAndSalary = list.stream()
.collect(Collectors.groupingBy(User::getStatus, Collectors.groupingBy(User::getAge, Collectors.groupingBy(User::getSalary))));
// 8. 自定义分组,先按工作状态分组,再自定义下一次分组key,比如小于30岁,key为青年,大于30岁,key为中年
// ==> { BUSY : { "青年" : List<User>, "老年" : List<User> } }
list.stream().collect(Collectors.groupingBy(User::getStatus, Collectors.groupingBy(u -> {
User user = (User)u;
if (user.getAge() < 30) {
return "青年";
} else {
return "中年";
}
})));
// ------------------ 分区 ------------------------
// 特殊情况下的分组形式,分组条件结果为true/false,即 { true : List<xx>, false : List<xx> }
// 9. 将工资大于6000的分一个区,小于等的分一个区 ==> {true : List<工资大于6000的User>, false : List<工资小于等于6000的User> }
Map<Boolean, List<User>> partitionSalaryMap = list.stream().collect(Collectors.partitioningBy(u -> u.getSalary() > 6000));
// ------------------ 统计 -----------------------
// 返回一个统计结果相关的操作类,可用来得到最大值,最小值等等,当要获取一个集合的各项情况时,这种方式比较好,必须要生成多次流操作获取
DoubleSummaryStatistics summaryStatistics = list.stream().collect(Collectors.summarizingDouble(User::getSalary));
double summaryStatisticsSum = summaryStatistics.getSum(); // 总和,工资总数
long summaryStatisticsCount = summaryStatistics.getCount(); // 总的个数,员工数
double summaryStatisticsMax = summaryStatistics.getMax(); // 最高工资
double summaryStatisticsMin = summaryStatistics.getMin(); // 最低工资
double summaryStatisticsAverage = summaryStatistics.getAverage(); // 平均工资