stream流的使用 (补充瑞吉外卖相关中的知识)

1.stream的介绍

Java8中的Stream是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时,它提供串行(单线程)和并行(多线程)两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势。通常,编写并行代码很难而且容易出错, 但使用Stream API无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。

我们可以将流看做流水线,这个流水线是处理数据的流水线,一个产品经过流水线会有一道道的工序就如同对数据的中间操作,比如过滤我不需要的,给数据排序能,最后的终止操作就是产品从流水线下来,我们就可以统一打包放入仓库了。

当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道:

 Stream有几个特性:

  1. Stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果

  2. Stream不会改变数据源,通常情况下会产生一个新的集合或一个值

  3. Stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行

2.Stream流的创建

注意:这个stream是collection的方法,所以collection的子类也都可以创建流;Map不是它的子类所以不行; 流不存放数据,所以对stream流操作进行debug也是看不到中间得操作的;

(1)Stream可以通过集合数组创建

使用集合的对象点一下,就可以发现与stream流相关的方法:

 1、通过 java.util.Collection.stream() 方法用集合创建流,我们发现:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}
List<String> list = Arrays.asList("a", "b", "c"); //把数组转化成集合对象
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();

(2)使用java.util.Arrays.stream(T[] array)方法用数组创建流

int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);

(3)使用Stream的静态方法:of()、iterate()、generate() 这里了解就行

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);

Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);

Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);

3.Stream的终止操作

为了方便我们后续的使用,我们先初始化一部分数据:

public class Person {
    private String name;  // 姓名
    private int salary; // 薪资
    private int age; // 年龄
    private String sex; //性别
    private String area;  // 地区

    public Person() {
    }

    public Person(String name, int salary, int age, String sex, String area) {
        this.name = name;
        this.salary = salary;
        this.age = age;
        this.sex = sex;
        this.area = area;
    }
    
    //get和set方法,tostring方法
}

初始化数据,我们设计一个简单的集合和一个复杂的集合。

后面的测试会直接使用这里构造的两个对象;

public class LambdaTest {

    //创建两个集合对象,后面做测试使用
    List<Person> personList = new ArrayList<Person>();
    List<Integer> simpleList = Arrays.asList(15, 22, 9, 11, 33, 52, 14);

        personList.add(new Person("张三",3000,23,"男","太原"));
        personList.add(new Person("李四",7000,34,"男","西安"));
        personList.add(new Person("王五",5200,22,"女","太原"));
        personList.add(new Person("小黑",1500,33,"女","上海"));
        personList.add(new Person("狗子",8000,44,"女","北京"));
        personList.add(new Person("铁蛋",6200,36,"女","南京"));
    
}

①遍历/匹配(foreach/find/match)

将数据流消费掉;

@Test
public void foreachTest(){
    // 打印集合的元素   这里的simpleList是上面创建的集合对象,这里为了方便就分开写了
    simpleList.stream().forEach(System.out::println);
    // 其实可以简化操作的
    simpleList.forEach(System.out::println);
}


@Test
public void findTest(){
    // 拿到第一个元素,后面我们学了流的排序,我们可以去取到排序中的第一个元素
    Optional<Integer> first = simpleList.stream().findFirst();
    // 随便找一个,可以看到findAny()操作,返回的元素是不确定的,
    // 对于同一个列表多次调用findAny()有可能会返回不同的值。
    // 使用findAny()是为了更高效的性能。如果是数据较少,串行地情况下,一般会返回第一个结果,
    
    // 如果是并行的情况,那就不能确保是第一个。 多线程帮你从里面任意取一个数据给你
    Optional<Integer> any = simpleList.parallelStream().findAny();
    System.out.println("first = " + first.get());
    System.out.println("any = " + any.get());
}

@Test
public void matchTest(){
    // 判断有没有任意一个人年龄大于35岁  personList是前面创建的引用对象
    // 任意一个元素与你定义的筛选条件相匹配,那么就会返回ture给你  item是集合中的泛型对象
    boolean flag = personList.stream().anyMatch(item -> item.getAge() > 35);
    System.out.println("flag = " + flag);

    // 判断是不是所有人年龄都大于35岁  需要所有的元素与你定义的筛选条件相匹配,才会返回ture给你
    flag = personList.stream().allMatch(item -> item.getAge() > 35);
    System.out.println("flag = " + flag);
}

②归集(toList/toSet/toMap)(有重点)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toListtoSettoMap比较常用。

下面用一个案例演示toListtoSettoMap

@Test
public void collectTest(){
    // 判断有没有任意一个人年龄大于35岁
    List<Integer> collect = simpleList.stream().collect(Collectors.toList());
    System.out.println(collect);
    Set<Integer> collectSet = simpleList.stream().collect(Collectors.toSet());
    System.out.println(collectSet);
    
    //转换成map有许多的注意事项
    //第一个参数表示选择person对象中的name作为map中的key  生成的map中的key值  要使用lambda表达式获取
    //第二个参数表示选择集合中的泛型对象作为map的value值   生成map中的value值  要使用lambda表达式获取
    //第三个参数表示,如果两个map中的key相同,那么选择第一个map中对应的value值作为value值
    //当然你可以把自己想要的参数设置为map的key或者是value,不过记得使用lambda表示式,如果不加第三个参数那么key冲突的时候就会抛异常,key不冲突就不会
     Map<String, Person> collect = personList.stream().collect(Collectors.toMap(Person::getName, p -> p, (k1, k2) -> k1));
        
    System.out.println(collect);
}

 

③ 统计(count/average/sum/max/min)

public class StreamTest {

    public static void main(String[] args) {
        List<Integer> simpleList = Arrays.asList(15, 22, 9, 11, 33, 52, 14);

        //获取平均结果
        OptionalDouble average = simpleList.stream().mapToInt(i -> i).average();
        System.out.println(average);
        //这种写法是用来判断数据是否存在的
        average.ifPresent(a->{
            System.out.println(a);
        });
        //最大值
        OptionalInt max = simpleList.stream().mapToInt(i -> i).max();
        System.out.println(max);
        //求和
        int sum = simpleList.stream().mapToInt(i -> i).sum();
        System.out.println(sum);
    }
}

运行结果:

 案例:获取员工工资的最大值:

Optional<Person> max = personList.stream().max((p1, p2) -> p1.getSalary() - p2.getSalary());
max.ifPresent(item -> System.out.println(item.getSalary()));

里边的比较器可以改为:Comparator.comparingInt(Person::getSalary)

④归约(reduce)

归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

案例:求Integer集合的元素之乘积。

@Test
public void reduceTest(){
    List<Integer> simpleList = Arrays.asList(15, 22, 9, 11, 33, 52, 14);
    //n1, n2代表结果和当前的数值   这个1表示初始值,如果做加法的话那么就要设置为0,如果不传的话,那么默认是0  n1*n2 的结果会重新赋值给n1
    Integer result = simpleList.stream().reduce(1,(n1, n2) -> n1*n2);
    System.out.println(result);
}

⑤分组(partitioningBy/groupingBy)

  • 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。

  • 分组:将集合分为多个Map,比如员工按性别分组。

 案例:

@Test
public void groupingByTest(){
    // 将员工按薪资是否高于5000分组  key为true的放在一个map中,key为false的放在一个map中
    Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 5000));
    // 将员工按性别分组
    Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
    // 将员工先按性别分组,再按地区分组
    Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
    System.out.println("员工按薪资是否大于5000分组情况:" + part);
    System.out.println("员工按性别分组情况:" + group);
    System.out.println("员工按性别、地区:" + group2);
}

 这个东西会用就行,不要去深挖里面的原理!

4.Stream中间操作

注意:我们所有的中间操作返回的值还是一个stream流,所以最后还是要进行终止操作的;

①筛选(filter 用得多)

该操作符需要传入一个function函数

List<Integer> simpleList = Arrays.asList(15, 22, 9, 11, 33, 52, 14);
//筛选出simpleList集合中大于17的元素,并打印出来
simpleList.stream().filter(item -> item > 17).forEach(System.out::println);

//打印工资大于500的人  这里的personList是前面创建的一个集合对象
personList.stream().filter(p -> p.getSalary()>500).forEach(System.out::println);

筛选员工中工资高于5000的人,并形成新的集合(可以理解为在消费数据流)。

List<Person> collect = personList.stream().filter(item -> item.getSalary() > 5000).collect(Collectors.toList());
System.out.println("collect = " + collect);

②映射(map/flatMap 用得多)

映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为mapflatMap

  • map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

就是一个n1经过一系列的操作变成了n2;

  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

案例:将员工的薪资全部增加1000。

		List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("张三",3000,23,"男","太原"));
        personList.add(new Person("李四",7000,34,"男","西安"));
        personList.add(new Person("王五",5200,22,"女","太原"));
        personList.add(new Person("小黑",1500,33,"女","上海"));
        personList.add(new Person("狗子",8000,44,"女","北京"));
        personList.add(new Person("铁蛋",6200,36,"女","南京"));

List<Person> collect = personList.stream().map(item -> {
            item.setSalary(item.getSalary() + 1000);
            return item;
        }).collect(Collectors.toList());
        System.out.println(collect);

 上面这种操作方式就是瑞吉外卖中经常使用到的一个操作,这个操作还可以对集合进行泛型的转换后面涉及到集合泛型的转换可以用这个来操作,非常的方便;

③排序(sorted)

sorted,中间操作。有两种排序:

  • sorted():自然排序,流中元素需实现Comparable接口

  • sorted(Comparator com):Comparator排序器自定义排序

案例:

@Test
public void sortTest(){
    // 按工资升序排序(自然排序)  可以自己传一个比较器,当然这里它也帮我们实现了相关的比较功能,调用相关的方法就行
    List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
        .collect(Collectors.toList());
    // 按工资倒序排序
    List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
        .map(Person::getName).collect(Collectors.toList());
    // 先按工资再按年龄升序排序
    List<String> newList3 = personList.stream()
        .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
        .collect(Collectors.toList());
    // 先按工资再按年龄自定义排序(降序)
    List<String> newList4 = personList.stream().sorted((p1, p2) -> {
        if (p1.getSalary() == p2.getSalary()) {
            return p2.getAge() - p1.getAge();
        } else {
            return p2.getSalary() - p1.getSalary();
        }
    }).map(Person::getName).collect(Collectors.toList());

    System.out.println("按工资升序排序:" + newList);
    System.out.println("按工资降序排序:" + newList2);
    System.out.println("先按工资再按年龄升序排序:" + newList3);
    System.out.println("先按工资再按年龄自定义降序排序:" + newList4);
}

运行结果:

 

其他不经常使用的操作就不一一列举了。

  • 7
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
瑞吉外卖是一个在线外卖订餐平台,以下是瑞吉外卖的顶层数据图的介绍: 瑞吉外卖顶层数据图主要描述了系统的整体功能和数据动的过程。它包括了四个主要的外部实体:用户、商家、配送员和支付系统。下面是对每个实体的功能和数据动的介绍: 1. 用户: - 注册/登录:用户可以通过注册或登录来访问瑞吉外卖平台。 - 浏览菜单:用户可以浏览不同商家的菜单,查看菜品信息和价格。 - 下单:用户可以选择菜品并下单,选择配送方式和支付方式。 - 评价:用户可以对已完成的订单进行评价和反馈。 2. 商家: - 提供菜单:商家可以上传菜单,包括菜品信息、价格和库存等。 - 接收订单:商家可以接收用户下的订单,并准备相应的菜品。 - 更新库存:商家在接收订单后需要更新菜品的库存信息。 3. 配送员: - 接单:配送员可以接收商家派发的订单,并准备配送。 - 配送:配送员将准备好的订单送达用户指定的地址。 - 更新订单状态:配送员在配送过程需要更新订单的状态,如已接单、配送、已送达等。 4. 支付系统: - 处理支付:支付系统负责处理用户的支付请求,包括支付方式的选择和支付金额的确认。 - 更新支付状态:支付系统在支付完成后需要更新订单的支付状态。 顶层数据图展示了以上实体之间的数据动和功能交互,它们通过不同的数据进行信息传递和处理。用户通过浏览菜单、下单和评价等操作与商家和配送员进行交互,支付系统负责处理支付请求并更新订单的支付状态。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值