Stream API
Stream 是 数据渠道, 用于操作数据源(集合, 数组等) 所生成的元素序列。
"集合讲的是数据, 流讲的是计算!"
Stream 自己不会存储元素
Stream 不会改变源对象,相反,他们会返回一个持有结果的新Stream
Stream操作是延迟执行的, 这意味着他们会等到需要结果的时候才执行
操作步骤
创建Stream: 一个数据源 获取一个流
可以通过Collection 系列集合提供的stream() 串行流 或 parallelStream() 并行流
可以通过Arrays的静态方法 stream() 获取 数组流
可以通过Stream类中的静态方法 of()
创建无限流
中间操作: 对数据源的数据进行处理 filter() map() 等
终止操作: 一个终止操作,执行中间操作链,并产生结果
创建Stream
创建 Stream 示例:
java@Test public void test(){ //1. 可以通过Collection 系列集合提供的stream() 串行流 或 parallelStream() 并行流 List<String> list = new ArrayList<>(); Stream<String> s1 = list.stream(); //2.可以通过Arrays的静态方法 stream() 获取 数组流 Integer[] s = {2,3,4,5}; Stream<Integer> s2 = Arrays.stream(s); //3.可以通过Stream类中的静态方法 of() Stream<String> ss = Stream.of("abc","123"); //4.创建无限流 //迭代 Stream<Integer> sss = Stream.iterate(0,x->x+2); sss.limit(4).forEach(System.out::println); //生成 Stream.generate(()->Math.random()).limit(4).forEach(System.out::println); }
中间操作
中间操作
多个中间操作 可以连起来形成一个流水线 , 除非流水线上触发终止操作,否则 中间操作不会执行任何的处理
而在 终止操作时一次性全部处理,成为 "惰性求值"
筛选与切片
filter----- 接收lambda, 从流中排除某些元素
limit(n)------ 截断流, 使其元素不超过给定数量n
skip(n)------- 跳过元素,返回一个扔掉了前n个元素的流, 若流中元素不足n个,则返回一个空流, 与 limit(n) 互补
distinct() --筛选, 通过流所生成元素的hashCode()和equals() 去除 重复元素
虽然代码中没有进行迭代调用,但 Stream API 完成了 内部迭代中间操作不会执行任何的处理
而在 终止操作时一次性全部处理,成为 "惰性求值"
java@Test public void test(){ List<Emp> emps = Arrays.asList( new Emp("王丽丽",4000,50), new Emp("李四",5000,40), new Emp("王丽丽2",6000,50), new Emp("刘莉莉",7000,60) ); // 筛选 薪资大于5000的员工信息 emps.stream() //创建流 .filter(x->x.getSalary()>5000) //中间操作 .forEach(System.out::println); // 终止操作 }
java@Test public void test(){ List<Emp> emps = Arrays.asList( new Emp("王丽丽",4000,50), new Emp("李四",5000,40), new Emp("王丽丽2",6000,50), new Emp("刘莉莉",7000,60) ); // 筛选 薪资大于5000的员工信息 emps.stream() .filter(x->x.getSalary()>4000) .limit(2) .forEach(System.out::println); }
如果 filter 筛选后 有 5 条 符合条件的, 那么 limit(2) 就是取 前2条 那么 skip(2) 就是 跳过前2条 ,往后取2条 distnict() 去重 要注意 实体类 重写 hashCode与equals 方法
映射
map()---- 接收 Lambda ,将 元素转换成其他形式 或提取信息, 接收一个函数作为参数, 该函数会被应用到每个元素上,并将其映射成一个新的元素
flatMap--- 接收一个函数作为参数, 将 流中的每个值 都换成另一个流, 然后把所有流连续成一个流
提取员工姓名:
java@Test public void test(){ List<Emp> emps = Arrays.asList( new Emp("王丽丽",4000,50), new Emp("李四",5000,40), new Emp("王丽丽2",6000,50), new Emp("刘莉莉",7000,60) ); // 提取员工姓名 emps.stream() .map(e->e.getName()) // 或 .map(Emp::getName) .forEach(System.out::println); }
map 与flatMap 关系 类似于 List 接口中 add() 与addAll() 在测试类中 编写如下代码 :
javaList<String> list1 =Arrays.asList("aaa","bbbb","ccc"); List list2 = new ArrayList(); list2.add(2222); list2.add(111); list2.add(list1); System.out.println(list2);
//此时 list2 为 [2222, 111, [aaa, bbbb, ccc]] , 长度为 3 在测试类 执行以下代码:
javaList<String> list1 =Arrays.asList("aaa","bbbb","ccc"); List list2 = new ArrayList(); list2.add(2222); list2.add(111); list2.addAll(list1); System.out.println(list2); System.out.println(list2.size());
//此时 list2 为 [2222, 111, aaa, bbbb, ccc] , 长度 为 5 ,
排序
sorted()----- 自然排序 --使用 Comparable
sorted(Comparator com)----- 定制排序-- 使用 Comparator
java@Test public void test(){ List<Emp> emps = Arrays.asList( new Emp("王丽丽",4000,50), new Emp("李四",5000,40), new Emp("王丽丽2",6000,50), new Emp("刘莉莉",7000,60) ); // 提取员工薪资 并 升序 排列 emps.stream() .map(Emp::getSalary) .sorted() .forEach(System.out::println); } // 定制排序 ,按照 年龄排 ,年龄相同 按照 姓名排, @Test public void test(){ List<Emp> emps = Arrays.asList( new Emp("王丽丽",4000,50), new Emp("李四",5000,40), new Emp("王丽丽2",6000,50), new Emp("刘莉莉",7000,60) ); // 提取员工姓名 emps.stream() .sorted((e1,e2)->{ if(e1.getAge() == e2.getAge()){ return e1.getName().compareTo(e2.getName()); }else{ return Integer.compare(e1.getAge(),e2.getAge()); // 升序, 可以返回负的 就是降序 } }).forEach(System.out::println); }
终止操作
终端操作 会从 流的流水线 生成结果, 其结果可以是任何不是流的值
查找与匹配
allMatch------ 检查是否匹配所有元素 ,返回值为 boolean
anyMatch-----检查是否至少匹配一个元素 返回值为 boolean
noneMatch---- 检查是否没有匹配所有元素 返回值为 boolean
findFirst---- 返回第一个元素, 返回Optional 对象, 可以通过get() 获得首个元素
findAny----- 返回当前流中的任意元素,返回Optional 对象, 可以通过get() 获得首个元素
count-----返回流中元素的总个数 , 返回long
max------ 返回流中最大值
min------返回流中最小值
forEach--- 内部迭代
java
//为 Emp.java 增加枚举类型 Status
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class Emp {
private String name;
private double salary;
private int age;
private Status status; //枚举类型
// 空闲, 忙碌 ,休假
public enum Status {
FREE,BUSY,VOCATION
}
}
//测试类
@Test
public void test(){
List<Emp> emps = Arrays.asList(
new Emp("王丽丽",4000,50, Emp.Status.FREE),
new Emp("李四",5000,40, Emp.Status.FREE),
new Emp("王丽丽2",6000,50,Emp.Status.FREE),
new Emp("刘莉莉",7000,60,Emp.Status.FREE)
);
//检测 员工是否都属于 BUSY 状态
boolean b = emps.stream()
.noneMatch(e -> e.getStatus().equals(Emp.Status.FREE));
System.out.println(b);
// 取 工资最高的 员工信息
Optional<Emp> first = emps.stream()
.sorted((e1, e2) -> -Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst();
System.out.println(first.get()); //获得 员工的相关信息
// stream() 串行流, parallelStream() 并行流
Optional<Emp> first = emps.parallelStream()
.filter(e->e.getStatus().equals(Emp.Status.BUSY))
.findAny();
System.out.println(first.get());
// 取 工资最高(最低)的 员工信息
Optional<Emp> max = emps.stream()
.max((e1,e2)->Double.compare(e1.getSalary(),e2.getSalary()));// 改为min
System.out.println(max.get());
}
归约与收集
归约:
reduce(T t,BinaryOpeartor op) 或 reduce(BinaryOpeartor op) --可以将流中的元素 反复 结合起来, 得到一个值
java@Test public void test(){ List<Integer> nums= Arrays.asList(1,2,3,4,5,6,7,8,9,10); // 对集合里的数 进行累加求和 Integer reduce = nums.stream() .reduce(0, (x, y) -> x + y); // 0 是 初始值, 第二个参数 要执行累加操作 System.out.println("sum="+reduce); //55 // 计算当前 所有员工的 薪资总和 List<Emp> emps = Arrays.asList( new Emp("王丽丽",4000,50, Emp.Status.BUSY), new Emp("李四",5000,40, Emp.Status.FREE), new Emp("王丽丽2",6000,50,Emp.Status.BUSY), new Emp("王丽丽3",9000,50,Emp.Status.BUSY), new Emp("王丽丽4",1000,50,Emp.Status.BUSY) ); // 有 初值0.0 返回 Double Double reduce1 = emps.stream() .map(e -> e.getSalary()) //提取薪资 .reduce(0.0, (e1, e2) -> e1 + e2); //薪资求和 Double::sum 也可以 System.out.println(reduce1); // 没有初值 返回 Optional 对象 Optional<Double> reduce2 = emps.stream() .map(e -> e.getSalary()) //提取薪资 .reduce(Double::sum); // 没有初值, 返回Optional System.out.println(reduce2.get()); }
要 注意 reduce 有没有初值时 ,返回值 不同
收集
collect--- 将 流 转换为其他形式, 接收一个Collector接口的实现, 用于给 Stream中元素汇总的方法
Collector 接口中 方法的实现 决定了 如何对流执行 收集操作 (如 收集到 List Set Map)
Collectors 实现类提供了 很多静态方法, 可以方便地创建常见收集器实例
示例:
java@Test public void test(){ List<Emp> emps = Arrays.asList( new Emp("王丽丽",4000,50, Emp.Status.BUSY), new Emp("李四",5000,40, Emp.Status.FREE), new Emp("王丽丽2",6000,50,Emp.Status.BUSY), new Emp("王丽丽3",9000,50,Emp.Status.BUSY), new Emp("王丽丽4",1000,50,Emp.Status.BUSY) ); // 收集 所有员工的姓名,并放到集合中去 List<String> collect = emps.stream() .map(Emp::getName) .collect(Collectors.toList()); collect.forEach(System.out::println); // 总工资的平均值 averagingDouble 平均值, summingDouble 求和,maxBy 最大, minBy 最小 Double avg = emps.stream() .collect(Collectors.averagingDouble(Emp::getSalary)); System.out.println(avg); // 分组, 计算 每个状态的 薪资总和 Map<Emp.Status, List<Emp>> group = emps.stream() .collect(Collectors.groupingBy(Emp::getStatus)); System.out.println(group); // 分区, 满足条件一个区, 不满足条件的一个区 // 薪资大于等于6000 为一区, 小于为 另一区 Map<Boolean, List<Emp>> collect = emps.stream() .collect(Collectors.partitioningBy(e -> e.getSalary() >= 6000)); System.out.println(collect); // 提取姓名, 并用 逗号 分隔, 首尾的逗号 均已去除 String str = emps.stream().map(Emp::getName).collect(Collectors.joining(",")); System.out.println(str); }
Stream Api 练习题
给定一个数字列表, 如何返回一个由每个数的平方构成的列表呢?
给定[1,2,3,4,5], 应该返回[1,4,9,16,25]
@Test public void test4(){ List<Integer> list = Arrays.asList(1,2,3,4,5); list.stream() .map(e->e*e) .forEach(System.out::println); }
怎样用 map 和reduce 方法 数一数 流中 有多少个Emp呢?
@Test public void test4(){ List<Emp> emps = Arrays.asList( new Emp("王丽丽",4000,50, Emp.Status.BUSY), new Emp("李四",5000,40, Emp.Status.FREE), new Emp("王丽丽22",8000,50,Emp.Status.VOCATION), new Emp("王丽丽4",1000,50,Emp.Status.VOCATION) ); Integer reduce = emps.stream() .map(e -> 1) .reduce(0, (e1, e2) -> e1 + e2); //.reduce(0,Integer::sum); System.out.println(reduce); }
//交易员类 @AllArgsConstructor @NoArgsConstructor @Data @ToString public class Trader { private String name; private String city; }
//交易类 @AllArgsConstructor @NoArgsConstructor @Data @ToString public class Transaction { private Trader trader; private int year; private int value; }
// 交易测试类 public class TestTrans { List<Transaction> transactions = null; @Before public void before(){ Trader t1 = new Trader("t1","tj"); Trader t2 = new Trader("t2","sh"); Trader t3 = new Trader("t3","tj"); Trader t4 = new Trader("t4","tj"); transactions = Arrays.asList( new Transaction(t4,2011,300), new Transaction(t1,2012,1000), new Transaction(t1,2011,400), new Transaction(t2,2012,710), new Transaction(t2,2012,700), new Transaction(t3,2012,950) ); } }
根据以上内容 编写测试类, 完成以下练习题
@Test public void test(){ //找出2011年发生的所有交易, 并按交易额排序, (从低到高) transactions.stream() .filter(x->x.getYear()==2011) .sorted((x1,x2)->Integer.compare(x1.getValue(),x2.getValue())) .forEach(System.out::println); //交易员都在哪些不同的城市工作过, transactions.stream() .map(e->e.getTrader().getCity()) .distinct() .forEach(System.out::println); //查找所有来自 tj的交易员,并按姓名排序 System.out.println("---------------3---------"); transactions.stream() .filter(x->x.getTrader().getCity().equals("tj")) .map(x->x.getTrader().getName()) .distinct() .sorted((x1,x2)->x1.compareTo(x2)) .forEach(System.out::println); //返回所有交易员的姓名字符串,按字母顺序排序 System.out.println("---------------4---------"); transactions.stream() .map(x->x.getTrader().getName()) .distinct() .sorted((x1,x2)->x1.compareTo(x2)) .forEach(System.out::println); //有没有交易员是在sh工作的? boolean sh = transactions.stream() .anyMatch(x -> x.getTrader().getCity().equals("sh")); System.out.println("------="+sh); //打印生活在tj的交易员的所有交易额 System.out.println("---6---"); Integer sum = transactions.stream() .filter(x -> x.getTrader().getCity().equals("tj")) .map(x -> x.getValue()) .reduce(0, (x1, x2) -> x1 + x2); System.out.println(sum); //所有交易中, 最高的交易额是多少 Optional<Integer> max = transactions.stream() .map(x -> x.getValue()) .max(Integer::compare); System.out.println("-------"+max.get()); //找到交易额最小的交易 Optional<Integer> min = transactions.stream() .map(x -> x.getValue()) .min(Integer::compare); System.out.println("-------"+min.get()); }