Java流(Stream)

Java流(Stream)

​ Java 8添加了一个新的抽象称为流Stream,可以让我们以一种声明的方式处理数据。当然新特性什么的就不说了,毕竟现在Java 16都出了,这算老特性了。只是平时工作中还是经常能用到,确实比较方便,所以今天写个总结。

​ 为了方便理解,就少来点理论,直接以实战代码为主进行分析。想要了解实现原理,建议看看源码,这里只讲使用方法,不扯远了,还是先来看看它的定义:

什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

简单的例子

​ 在了解Java流之前,必须要先知道lambda表达式的用法,如果不了解lambda表达式,可以先看看这位大神的lambda表达式详解

​ 先列举一些常用的Java流的方法,免得不知道自己在干嘛,当然方法不止这些,我只写了些我自己常用的。常用方法:

  • stream:返回此集合的流。这是Collection接口下的方法,继承了Collection的List和Set都能用。
  • collect:使用Collector对此流的元素进行可变缩减操作,说人话就是,流的收集器。
  • min:求出流中的最小值。
  • max:求出流中的最大值。
  • limit:截取流中的元素并返回。
  • count:统计流中的元素个数。
  • filter:对流进行过滤。
  • sort:对流中的元素进行排序。
  • distinct:返回该流不同元素组成的流,说白了就是去重。
  • map:返回由给定函数应用于此流的元素的结果组成的流。
  • concat:合并两个流,组合成新流,哈哈,是不是很眼熟。
  • reduce:Java流中的聚合函数,这算是一个核心了,像数据库里的count 、sum 、avg 、max 、min 等函数就是一种聚合操作。

​ 有一个列表,从中筛选出值大于30的元素。看看用传统的做法和Java流的做法有什么不同。

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(20);
list.add(40);
list.add(100);        
// 按照以前的方法   
List<Integer> list1 = new LinkedList<>();     
for (Integer value : list) {       
    if (value > 30) {          
        list1.add(value);     
    }     
}     
System.out.println(list1);     
// 使用java流
// 详细讲解一下,后面我就偷懒了
// list.stream()就是先将集合处理一下,返回我们需要的流
// filter的方法就是这里帮我们筛选值大于30的方法
// 这里用的是lambda表达式,所以要先了解一下lambda表达式,
// filter(Predicate<? super T> predicate)方法的参数是Predicate类型,Predicate是一个函数式接口
// 当把数据筛选出来后,此时还需要把它变成List的集合,毕竟现在还只是流,不是我们要的
// 这时候就需要collect方法了,这就是我说的流收集器,其实不止可以转换成List,还有Collectors.toSet()和Collectors.toMap()
List<Integer> list2 = list.stream().filter(value -> {
    return value > 30;      
}).collect(Collectors.toList());   
System.out.println(list2);

​ 光从这个例子看,并没法很直观的感觉出Java流的好处,也没觉得好用到哪儿去,和传统方法比起来代码量也没减少多少嘛。的确,这看上去也差不多,那么下面啥也不说了,直接上实战。

实战

​ 在实际工作中,有些时候在特殊条件下,需要自己在代码里对数据进行一些处理,如果能直接从数据库查询出来直接用当然完美,但那也只是理想情况。所以,一个成熟的程序员,要学习自己处理数据。这里的@Data注解有用到lombok,不了解的可以先看看这位大神的文章lombok

​ 这里有两个实体类,一个用户类,一个任务类,下面将列举多种情况,看看用Java流是怎么处理的(并对比一下,如果没有Java流,按照传统的做法,是该怎么做)。

@Data
public class User {
    /**
     * 用户id
     */
    private int id;
    
    /**
     * 用户年龄
     */
    private int age;

    /**
     * 用户名称
     */
    private String name;

    /**
     * 用户电话
     */
    private String phoneNumber;
}
@Data
public class Task {
    /**
     * 任务id
     */
    private int id;

    /**
     * 接任务用户的用户id
     */
    private int userId;

    /**
     * 任务描述
     */
    private String taskContent;

    /**
     * 任务奖金
     */
    private int bonus;
}
  1. 场景一:系统需要获取任务信息包括接任务的用户名称做一个数据展示,但不需要年龄和电话号码,需要返回的大概是这种数据。
public class TaskVo {
    /**
     * 任务id
     */
    private int id;

    /**
     * 用户名称
     */
    private String userName;

    /**
     * 任务描述
     */
    private String taskContent;

    /**
     * 任务奖金
     */
    private int bonus;
}
public class test {
    public static void main(String[] args) {
        // 先来构建数据
        List<User> users = Arrays.asList(new User(1, "小明", 17, "13879873219")
                , new User(2, "小红", 20, "13879873218")
                , new User(3, "小陈", 39, "13879873217"));

        List<Task> tasks = Arrays.asList(new Task(1, 1, "扶老奶奶过马路", 100)
                , new Task(2, 2, "帮助程序员A找到系统bug", 100)
                , new Task(3, 3, "给作者点个赞再走吧", 10000));
        // 这里将数据转换成我们需要的一个Map,以用户id为key,用户的name为value
        Map<Integer, String> userMap = users.stream().collect(Collectors.toMap(User::getId, User::getName));
        // 使用Java流将数据处理成我们需要的样子
        List<TaskVo> list = tasks.stream().map(task -> {
            // map方法的用处我已经在前面写过了,这里是具体的用法。
            // 下面是构建我需要的一个TaskVo对象并返回
            // 当然网上有很多对象转换的方式和工具,我这里对象的属性少,这样写无所谓,正式开发可能遇到那种比较多的,所以别学我,也别问我为什么不用工具,作者不想回答你并向你扔了一只狗
            TaskVo vo = new TaskVo();
            vo.setBonus(task.getBonus());
            vo.setId(task.getId());
            vo.setTaskContent(task.getTaskContent());
            // 注意这里的用户名是从userMap里取的哟
            vo.setUserName(userMap.get(task.getId()));
            return vo;
        }).collect(Collectors.toList());
        // 打印出来瞧瞧
        list.stream().forEach(System.out::println);
    }
}

在这里插入图片描述

  1. 场景二:公司需要统计一下接了奖金1000以上(包含1000)的任务的人,能接1000以上的人都是有能力的人,所以需要重点关注,看能不能发展成公司的核心用户,并按照奖金把高的排在前面,低的排后面。
public class test {
    public static void main(String[] args) {
        // 构建数据
        List<User> users = Arrays.asList(new User(1, "小明", 17, "13879873219")
                , new User(2, "小红", 20, "13879873218")
                , new User(3, "小陈", 39, "13879873217")
                , new User(4, "小李", 25, "13879873217"));

        List<Task> tasks = Arrays.asList(new Task(1, 1, "给同事带杯奶茶", 1)
                , new Task(2, 2, "帮助程序员A找到系统bug", 100)
                , new Task(3, 3, "给作者点个赞再走吧", 1000)
                , new Task(4, 4, "如果喜欢收藏一下再走吧", 10000));
		// 这里是干嘛的就不说了吧
        Map<Integer, String> userMap = users.stream().collect(Collectors.toMap(User::getId, User::getName));
        // 筛选出我们需要的人才
        List<String> list = tasks.stream().filter(task -> {
            // 筛选出金额大于等于1000的
            return task.getBonus() >= 1000;
        }).sorted((t1, t2) -> {
            // 排序
            return t2.getBonus() - t1.getBonus();
        }).map(task -> {
            // 取出用户名
            return userMap.get(task.getUserId());
        }).collect(Collectors.toList());
        // 做一下打印,有能力的人都是接的什么任务我就不多说了吧,你懂的
        list.stream().forEach(System.out::println);
    }
}

在这里插入图片描述

  1. 场景三:公司需要预计一下用户完成任务后本月需要结算给用户的任务奖金,这样的话,财务好早做准备。
public class test {
    public static void main(String[] args) {
        List<Task> tasks = Arrays.asList(new Task(1, 1, "给同事带杯奶茶", 1)
                , new Task(2, 2, "帮助程序员A找到系统bug", 100)
                , new Task(3, 3, "给作者点个赞再走吧", 1000)
                , new Task(4, 4, "如果喜欢收藏一下再走吧", 10000));
        // 计算所有任务金额,这里就是reduce方法的作用,也就是聚合函数,就实现了类似数据库的sum函数了
        Integer money = tasks.stream().map(Task::getBonus).reduce((obj1, obj2) -> {
            // 这里的话,就可以看成reduce方法会把流中的每一个元素取出,并进行加法操作
            return obj1 + obj2;
        }).get();
        System.out.println(money);
    }
}

在这里插入图片描述

​ 当然reduce还有一个重载方法,有两个参数(其实还有一个重装方法,有三个参数,不过我几乎没用过,实际开发也很少用到,所以有兴趣的可以自己去了解一下)。第一个参数是作为初始值来使用的,第二个参数就和前面一个参数一样的。

​ 举个实际点的例子,公司除了要预计本月看得见的任务奖金,还要预留一部分钱,以防止某个牛人在月底突然接了任务然后以极快的速度把它给完成了,到时候不给人家结算也不好是吧。

public class test {
    public static void main(String[] args) {
        List<Task> tasks = Arrays.asList(new Task(1, 1, "给同事带杯奶茶", 1)
                , new Task(2, 2, "帮助程序员A找到系统bug", 100)
                , new Task(3, 3, "给作者点个赞再走吧", 1000)
                , new Task(4, 4, "如果喜欢收藏一下再走吧", 10000));
        // 这里获取值的有点细微的区别,仔细看看哦
        Integer money = tasks.stream().map(Task::getBonus).reduce(1000, (obj1, obj2) -> {
            return obj1 + obj2;
        });
        System.out.println(money);
    }
}

在这里插入图片描述

​ reduce方法是很核心的方法,只要理解了它的用法,那么max、min、sum等等聚合函数的实现都可以通过reduce方法完成。

其他方法的使用就不一一列举了,本文主要记录一下使用的思路。总结一下,Java流让我们开发的时候能更方便的处理数据,并且使用的时候就感觉像数据库的SQL语句。需要注意的是,在Streams开始和结束之前,都需要避免处理null值,使用filter可以过滤掉。

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

佛祖保佑永不宕机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值