Stream API的使用

建议参考文章《用了多年Java,你的团队会使用Java8 Stream API吗?》
Stream是Java8中处理数组、集合的抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。

一个Stream表面上与一个集合很类似,集合中保存的是数据,而流中对数据的操作,类似于流水线的操作。

Stream的特点:

  1. Stream 自己不会存储元素
  2. 不会改变源对象。相反,他们会返回一个持有结果的新Stream
  3. 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
  4. 遵循“做什么,而不是怎么去做”的原则。只需要描述需要做什么,而不用考虑程序是怎样实现的

使用Stream,会有三个阶段(步骤):

  1. 创建一个Stream
  2. 在一个或多个步骤中,将初始Stream转化到另一个Stream的中间操作
  3. 使用一个终止操作来产生一个结果。该操作会强制他之前的延迟操作立即执行。在这之后,该Stream就不会在被使用了

Stream的创建方法

//Stream.of方法
Stream<String> stream = Stream.of("hello", "xxx", "yyyy", "beijing", "shanghai");

//Arrays.stream方法
IntStream stream1 = Arrays.stream(new int[]{100, 200, 300, 400, 500});

//集合方法【常用】
List<String> list = new ArrayList<>();
Stream<String> stream2 = list.stream();

//并行流:多核,充分发挥CPU,提高效率
Stream<String> stringStream = list.parallelStream();

Stream中间操作

中间操作包括:map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。

多个中间操作可以连接起来形成一个流水线,除非流水 线上触发终止操作,否则中间操作不会执行任何的处理! 而在终止操作时一次性全部处理,称为“惰性求值”。

一种一点到底的操作。

代码演示各种方法的操作,这里首先创建了集合,并引入了诸多元素(包含重复de)。

List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Jack", 20, "男", 65000));
employees.add(new Employee("Peter", 27,"nv", 80000));
employees.add(new Employee("鬼头", 23,"男", 60000));
employees.add(new Employee("鬼头", 23,"男", 60000));
employees.add(new Employee("Boss", 30,"女", 10000));
employees.add(new Employee("Boss", 30,"女", 10000));
employees.add(new Employee("鸣人", 16, "男",27000));
employees.add(new Employee("鸣人", 16,"男", 27000));
filter 筛选
//筛选年龄大于25的,并遍历
employees.stream()
	.filter(e -> e.getAge() > 25)
    .forEach(System.out::println);
limit——截断流,使其元素不超过给定数量
//只展示两个元素并遍历
employees.stream()
	.limit(2)
	.forEach(System.out::println);
skip(n) —— 跳过元素
//跳过两个元素,然后只展示后面元素中的两个
employees.stream()
	.skip(2)
	.limit(2)
	.forEach(System.out::println);
distinct去重
//筛选,通过流所生成元素的 equals() 去除重复元素,所以必须要重写equals方法
employees.stream()
	.distinct()
	.forEach(System.out::println);
map映射
//map映射方法获取姓名并遍历
employees.stream()
	.distinct()
	.map(e -> e.getName())
	.forEach(System.out::println);
sorted排序
//自然排序
mployees.stream()
	.distinct()
	.map(Employee::getAge)
	.sorted()
	.forEach(System.out::println);
//定制排序
employees.stream()
	.distinct()
	.sorted((o1, o2) -> {
	//这里注意,double类型数据一定使用【Double.compare(o1, o2)方法进行比较】
	return (o1.getAge() - o2.getAge()) == 0 ? Double.compare(o2.getSalary(), o1.getSalary()) : (o1.getAge() - o2.getAge());
	})
	.forEach(System.out::println);

//【建议使用】:Comparator.comparing,Comparator.reverseOrder()是倒序
.sorted(Comparator.comparing(e -> e.getAge, Comparator.reverseOrder()))	

Stream终止操作

终止操作包括:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。、

allMatch查是否匹配所有元素
boolean allMatch = employees.stream()
	.allMatch(e -> e.getGender().equals("男"));
anyMatch检查是否至少匹配一个元素
boolean anyMatch = employees.stream()
	.anyMatch(e -> e.getGender().equals("女"));
noneMatch检查是否没有匹配的元素
boolean noneMatch = employees.stream()
	.noneMatch(e -> e.getGender().equals("妖"));
findFirst返回第一个元素 / findAny——返回当前流中的任意元素
Employee employee = employees.stream()
	.findFirst()
    .get();
System.out.println(employee);
count返回流中元素的总个数
long count = employees.stream()
	.count();
max返回流中最大值 / min返回流中最小值
Employee max = employees.stream()
	.max((o1, o2) -> {
		return Double.compare(o1.getSalary(), o2.getSalary());
	 }).get();
System.out.println(max);

Employee employee1 = employees.stream()
	.min((o1, o2) -> {
		return o1.getAge() - o2.getAge();
	}).get();
System.out.println(employee1);

//【与排序一样,建议使用Comparator.comparing中的方法】
reduce归约
//reduce归约_可以将流中元素反复结合起来,得到一个值
reduce(T identity, BinaryOperator);
reduce(BinaryOperator);

//求和
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().reduce(0, (x, y) -> x + y);

//计算员工的工资总和
 Double reduce = employees.stream()
	.map(e -> e.getSalary())
	.reduce(0.0, (x, y) -> x + y);
collect 将流转换为其他形式

接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法。

//获取所有员工的姓名集合
List<String> list = employees.stream()
	.map(e -> e.getName())
	.collect(Collectors.toList());

Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行,并行parallelStream()用法与串行Stream一样。

分组

    public void CollectorsTest(){
        Student stu1 = new Student("stu1",11);
        Student stu2 = new Student("stu2",12);
        Student stu3 = new Student("stu3",13);
        Student stu4 = new Student("stu4",14);
        Student stu5 = new Student("stu5",15);
        List<Student> list = Lists.newArrayList(stu1,stu2,stu3,stu4,stu5);
        //将所有学生的姓名收集到新的list中
        List<String> nameList = list.stream().map(Student::getName).collect(Collectors.toList());
        //同样可以收集到Set中
        Set<String> nameSet = list.stream().map(Student::getName).collect(Collectors.toSet());
        //将每个学生的name,age转换成Map,若有名称相同的会造成key相同导致失败
        Map<String,Integer> map = list.stream().collect(Collectors.toMap(Student::getName,Student::getAge));
        //字符串连接
        String nameJoin = list.stream().map(Student::getName).collect(Collectors.joining(";","(",")"));

        //---聚合操作----
        //count
        Long count = list.stream().collect(Collectors.counting());
        // max
        Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get();
        // sum
        Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge));
        // avg
        Double avgAge = list.stream().collect(Collectors.averagingDouble(Student::getAge));
        // 所有聚合操作的返回
        DoubleSummaryStatistics summaryStatistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
        summaryStatistics.getSum();
        summaryStatistics.getAverage();
        summaryStatistics.getCount();
        summaryStatistics.getMax();
        summaryStatistics.getMin();

        //group 按年龄分组
         Map<Integer,List<Student>> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getAge));

         //分区,分成两部分,一部分大于12岁,一部分小于等于12岁
        Map<Boolean,List<Student>> agePartition = list.stream().collect(Collectors.partitioningBy(x -> x.getAge() > 12));

        //规约
        Integer reducingAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::max)).get();

    }
    
    public void countTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        Long count = list.stream().count();
    }

    public void maxAndminTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        Integer max = list.stream().max(Integer::compareTo).get();
        Integer min = list.stream().min(Integer::compareTo).get();
    }

    public void reduceTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        Integer sum = list.stream().reduce(0,(x,y) -> x + y);
    }
实操案例1

场景:有一个产品的集合,我们需要根据其产品名称进行数量分类

//(1)使用collect.toMap处理,对key冲突处理
Map<String, Integer> statistic = requestStatisticDOS.stream().collect(Collectors.toMap(
                Product::getName,
                v -> 1,
                (o, n) -> ++o));

默认必须赋值1,数据存在即为1个,另外,第三个参数是key 冲突处理,里面的o和n表示oldKey和newKey,这里必须是oldKey进行先++操作,这样返回的才是加之后的数据。

//(2)使用groupingBy
Map<String, Long> collect = requestStatisticDOS.stream().collect(Collectors.groupingBy(Product::getName, Collectors.counting()));
实操案例2
/**
         * 场景:
         * 数据库查询条件证件号、姓名、会员等级
         * 返回会员集合List<Map<String, String>>类型
         * 1、过滤关键字段,证件号:identifyId、姓名、等级都不能为空
         * 2、根据证件号,去重
         * 3、根据等级由大到小倒序排列
         * 4、截取,只返回三个数据即可
         */
//模拟数据,我们得到如下的数据 members
{"level":"9","name":"张三9","identifyId":"1306841999"}
{"level":"4","name":"张三4","identifyId":"1306841994"}
{"level":"1","name":"张三1","identifyId":"1306841991"}
{"level":"3","name":"张三3","identifyId":"1306841993"}
{"level":"6","name":"张三6","identifyId":"1306841996"}
{"level":"7","name":"张三7","identifyId":"1306841997"}
{"level":"5","name":"张三5","identifyId":"1306841995"}
{"level":"3","name":"张三3","identifyId":"1306841993"}
{"level":"6","name":"张三6","identifyId":"1306841996"}
{"level":"8","name":"张三8","identifyId":"1306841998"}
{"level":"9","name":"张三9","identifyId":"1306841999"}
{"level":"2","name":"张三2","identifyId":"1306841992"}
{"level":"10","name":"张三10","identifyId":"13068419910"}  

//对数据进行流式处理
members = members.stream()

                //1、过滤
                .filter(e -> StringUtils.isNotEmpty(e.get("identifyId"))
                        && StringUtils.isNotEmpty(e.get("name"))
                        && StringUtils.isNotEmpty(e.get("level")))

                //2、根据证件号排重:分组map<"identifyId", map<String, String>>
                .collect(Collectors.toMap(key ->
                        key.get("identifyId"), //选取证件号为key
                        value -> value, //value就是成员Map本身
                        (o, n) -> o)) //如果key冲突,Old和New,选择Old,排重操作
                .values() //获取所有的map

                //3、排序,Comparator.reverseOrder()降序
                .stream().sorted(Comparator.comparing(e -> e.get("level"), Comparator.reverseOrder()))

                //截取
                .limit(5)

                //转为list
                .collect(Collectors.toList());     

//打印结果
{"level":"9","name":"张三9","identifyId":"1306841999"}
{"level":"8","name":"张三8","identifyId":"1306841998"}
{"level":"7","name":"张三7","identifyId":"1306841997"}
{"level":"6","name":"张三6","identifyId":"1306841996"}
{"level":"5","name":"张三5","identifyId":"1306841995"}  
实操案例3——faltMap

flatMap可以在循环里新建集合,最后collect的时候进行组合返回结果

/**
 * 商品类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goods {

    /**
     * 商品id
     */
    private Long goodId;

    /**
     * 商品名称
     */
    private String goodName;

    /**
     * 商品价格
     */
    private String goodPrice;
}

/**
 * 品牌类:包含一系列商品
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Brand {

    /**
     * 品牌id
     */
    private Long id;

    /**
     * 品牌名称
     */
    private String brandName;

    /**
     * 包含的商品
     */
    private List<Goods> goodsList;
}

/**
 * 商圈类:包含一系列品牌
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BusinessCircle {

    /**
     * 品牌集合
     */
    private List<Brand> brandList;
}

/**
 * 验货任务表,商品维度
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CheckGoodsTask {

    /**
     * 商品的id
     */
    private Long goodsId;

    /**
     * 商品名称
     */
    private String goodsName;

    /**
     * 对应品牌的id
     */
    private Long brandId;
}

场景:①形成Map;②形成计划任务

@RunWith(JUnit4.class)
public class JdkStreamTest {

    /**
     * 测试 flatMap
     */
    @Test
    public void testFlatMap() {
        List<BusinessCircle> circles = getBrandList();

        /*
         * 我们的目的是将List<BusinessCircle>转为Map
         * Map的 key = 品牌的id
         * Map的 value = Set<String>商品的名称的set集合
         * 很显然,我们需要进行商圈->品牌->商品的三重过滤,然后组合成结果
         * 我们可以考虑使用flatMap展开处理,返回内层list的steam进行进一步操作
         */
        Map<String, Set<String>> result = circles.stream()

                /* 我们是不需要商圈信息的,所以这里我们可以直接展开内层的品牌list
                 * 源码:<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
                 * 显然需要返回一个stream,以方便后续操作
                 */
                .flatMap(v -> v.getBrandList().stream())
                .collect(Collectors.toMap(
                        k -> String.valueOf(k.getId()),
                        v -> v.getGoodsList().stream()
                                .map(Goods::getGoodName)
                                .collect(Collectors.toSet()),
                        (o ,n) -> o));
        System.out.println(JackSonUtil.objToJson(result));

        /*
         * 新的要求:遍历获取商品,并以商品维度填充自动盘货计划任务表对象
         * CheckGoodsTask:里面包含有商品的id和名称,以及其对应的品牌id
         * 同样涉及到内层嵌套循环,还是使用flatMap
         */
        List<CheckGoodsTask> collect = circles.stream()
                .flatMap(c -> c.getBrandList().stream())
                .flatMap(b -> {

					// 这里可以内部新建List,最终collect规约的时候将全部list组合
                    List<CheckGoodsTask> tasks = Lists.newArrayList();
                    b.getGoodsList()
                            .forEach(g ->
                                tasks.add(new CheckGoodsTask(
                                        g.getGoodId(),
                                        g.getGoodName(),
                                        b.getId()))
                            );

                    //注意;flatMap必须给Stream类型返回值,方便后续操作
                    return Stream.of(tasks);
                })

                //以上数据返回结果是Stream<List<CheckGoodsTask>>
                //如果直接Collectors.toList那么将按商圈维度返回List<List<CheckGoodsTask>>
                //需要再次展开,集合成一个list
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
        System.out.println(JackSonUtil.objToJson(collect));
    }

    /**
     * 获取一系列的商圈,每个商圈有一系列品牌,每个品牌有一系列的商品,这样构成一个三维的list集合
     * @return 商圈list集合
     */
    private List<BusinessCircle> getBrandList() {
        List<BusinessCircle> circles = new ArrayList<>(2);
        for (int k = 0; k < 2; k++) {
            List<Brand> brands = new ArrayList<>(5);
            for (int i = 0; i < 5; i++) {
                List<Goods> goodsList = new ArrayList<>(3);
                for (int j = 0; j < 3; j++) {
                    goodsList.add(new Goods((long) j,
                            "商品" + j,
                            String.valueOf(j * 20)));
                }
                brands.add(new Brand((long)i,
                        "品牌" + k,
                        goodsList));
            }
            circles.add(new BusinessCircle(brands));
        }
        return circles;
    }
}
实操案例4——对集合中元素的属性赋值
/**
 * 对list中元素进行赋值
 */
@Test
public void testSetInList() {
    List<Goods> goodsList = new ArrayList<>(3);
    goodsList.add(new Goods(1L, "苹果", "22"));
    goodsList.add(new Goods(2L, "鸭梨", "55"));
    goodsList.add(new Goods(3L, "香蕉", "99"));
    
    //使用peek,可以对值进行修改
    goodsList = goodsList.stream()
            .peek(v -> v.setGoodName(v.getGoodName() + "Cn"))
            .collect(Collectors.toList());
	
	//也可以使用map进行处理,但是注意必须返回元素,因为collect规约的时候会对元素进行合并
	goodsList = goodsList.stream()
                .map(v -> {
                    v.setGoodPrice(v.getGoodPrice()+"$");
                    return v;
                })
                .collect(Collectors.toList());

	//可以使用map进行复杂的分组
	//比如将list转为Map<GoodName, Set<GoodPrice>>
	Map<String, Set<String>> collect = goodsList.stream()
        .collect(Collectors.toMap(
                Goods::getGoodName,
                v -> {
                    Set<String> set = Sets.newHashSet();
                    set.add(v.getGoodPrice());
					//返回集合,后面key冲突的时候排重合并
                    return set;
                },
                (o, n) -> {
					//如果key冲突,使用addAll将集合并入
                    o.addAll(n);
                    return o;
                }
        ));

	//还可以使用 Collectors.groupingBy进行处理
	Map<String, Set<String>> result = goodsList.stream()
        .collect(
                Collectors.groupingBy(
                        Goods::getGoodName,
                        Collectors.mapping(Goods::getGoodPrice, Collectors.toSet())));

	//使用map处理
    System.out.println(JackSonUtil.objToJson(result));
}

//打印结果
{"苹果Cn":["22$","55$"],"香蕉Cn":["99$"]}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值