浅聊java8中数值流的使用

 更新时间:2023年10月27日 15:37:29   作者:shark_chili  
java8为我提供的简单快捷的数值流计算API,本文就基于几个常见的场景介绍一下数值流API的使用,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

简介

java8为我提供的简单快捷的数值流计算API,本文就基于几个常见的场景介绍一下数值流API的使用。

基础示例

我们以一个食物热量计算的功能展开演示,如下所示,可以看到Dish类它记录了每一个食物的名称、热量、类型等信息:

public class Dish {

    /**
     * 名称
     */
    private final String name;
    /**
     * 是否是素食
     */
    private final boolean vegetarian;
    /**
     * 卡路里
     */
    private final int calories;
    /**
     * 类型
     */
    private final Type type;

    //类型枚举 分别是是:肉类 鱼类 其他
    public enum Type {MEAT, FISH, OTHER}

    public Dish(String name, boolean vegetarian, int calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    //...... get set


}

基于这个食物类,我们给出一个食物类的集合作为模拟数据:

public static final List<Dish> menuList =
            Arrays.asList(
                    new Dish("pork", false, 800, Dish.Type.MEAT),
                    new Dish("beef", false, 700, Dish.Type.MEAT),
                    new Dish("chicken", false, 400, Dish.Type.MEAT),
                    new Dish("french fries", true, 530, Dish.Type.OTHER),
                    new Dish("rice", true, 350, Dish.Type.OTHER),
                    new Dish("season fruit", true, 120, Dish.Type.OTHER),
                    new Dish("pizza", true, 550, Dish.Type.OTHER),
                    new Dish("prawns", false, 400, Dish.Type.FISH),
                    new Dish("salmon", false, 450, Dish.Type.FISH)
            );

我们希望计算出这个菜肴集合的总热量,我们可能会这样写:

 public static void main(String[] args) {
        
        int total = menuList.stream()
                //获取每个食物的卡路里
                .map(Dish::getCalories)
                //调用reduce,从0开始累加每个食物的热量
                .reduce(0, Integer::sum);

        System.out.println(total);
    }

输出结果如下:

4300

尽管它尽可能的简洁并计算出了总热量,但是它存在许多隐患,首先时map时,它会将基本类型的calories装箱成Integer,这一点我们查看map的返回值即可知晓。

Stream<Integer> integerStream = menuList.stream()
                //获取每个食物的卡路里
                .map(Dish::getCalories);

因为拿到的是包装类的流,调用reduce进行数值计算时,有需要对其进行拆箱,拆箱时就会调用到IntegerintValue方法:

public int intValue() {
        return value;
    }

所以若在大量数值计算的情况下,频繁的拆箱和装箱势必导致程序的执行效率低下。

特值流

那么有没有什么办法可以保证在数据收集的时候避免频繁装箱和拆箱呢?答案是特化流,就以本案例来说,我们在数值收集的时候直接调用mapToInt方法,通过该方法即可得到每一个数值的特值流IntStream,随后我们直接调用特值流计算方法sum即可完成热量统计:

对应的代码示例如下:

public static void main(String[] args) {
        int total = menuList.stream()
                //将每一个卡路里转换为特值流IntStream
                .mapToInt(Dish::getCalories)
                //将所有数值累加
                .sum();

        System.out.println(total);
    }

最终输出结果也是4300:

4300

相较于reduce方法,特值流提供了更多更方便的计算API

  • average:计算所有数值的平均数。
  • count:获取数值总数。
  • max:获取收集数据中的最大值。
  • min:获取收集数据中的最小值。

特化流还原会原始流

有时候我们希望这些特化流转为原始流即包装类的流,那么我们可直接调用boxed方法完成对特值流的装箱:

 public static void main(String[] args) {

        
        Stream<Integer> integerStream = menuList.stream()
                //拿到所有数值的特值流
                .mapToInt(Dish::getCalories)
                //将所有特值流装箱
                .boxed();

        //输出特值流对象的数值
        integerStream.forEach(i -> System.out.println(i));

    }

特化流空数值问题

我们都知道特化流可以直接获取收集到数值的最大值或者最小值,我们假设这样一个场景,食物类对象的卡路里字段为Integer

private final Integer calories;

并且我们食物类的集合为空:

 public static final List<Dish> menuList = new ArrayList<>();

面对可能存在的空结果问题,要如何解决呢?

实际上java8已经考虑到这个问题了,当我们调用max等计算API获取结果时,它实际返回的对象是OptionalInt,该对象提供了各种API用于判断数值是否为空,当我们最大值为空,就直接返回1时,我们可以直接使用orElse方法:

public static void main(String[] args) {

        OptionalInt max = menuList.stream()
                .mapToInt(Dish::getCalories)
                .max();
        
        //不存在最大值时,直接返回1
        System.out.println(max.orElse(0));


    }

亦或者我们需要判断是否存在最大值时,可以直接调用isPresent方法:

public static void main(String[] args) {

        OptionalInt max = menuList.stream()
                .mapToInt(Dish::getCalories)
                .max();

        //若存在最大值直接返回true
        System.out.println(max.isPresent());


    }

数值流的范围操作

我们希望统计1-100之间的偶数数量,在java8之前,你可能会这样做:

  • for循环1-100。
  • 判断是否是偶数。
  • 如果是偶数,则临时变量count自增一下。

java8的步骤则精简许多:

  • 基于特值流生成1-100全闭区间数据。
  • 过滤出偶数。
  • 调用count进行统计。
public static void main(String[] args) {
        //生成1-100全闭区间数据
        long count = IntStream.rangeClosed(1, 100)
                //过滤出偶数
                .filter(i -> i % 2 == 0)
                //计算统计结果
                .count();

        System.out.println(count);


    }

输出结果:

50

当然,如果你要生成左闭右开即1-99,则可以调用range方法生成:

IntStream.rangeClosed(1, 99)

数值流的应用——勾股数

现在我们来写一个获取1-100以内前3个勾股数的小功能。由公式:

a^2 + b^2=c^2

可知,要想得到勾股数,我们只需判断a^2 + b^2的和再开根号是否可以被整除,即:

Math.sqrt(a * a + b * b) % 1 == 0

所以我们可以按照下面这样的步骤执行:

  • 创建1-100全闭区间作为第一条边a。
  • 为避免计算的勾股数重复,出现[3,4,5][4,3,5]这种情况,我们的第二条边b范围为a-100。
  • 拿着a和b,计算这两个数值的平方和再开根号看看是否为整数。
  • 将开根号结果为整数的结果生成数组。
  • 获取前3个这样的数组。

所以我们写出下面这段代码,需要注意的是笔者在生成b的时候用到了flatMap,原因很简单,因为生成a时boxed返回的对象是Stream<Integer>,假如把这个流直接用map和b进行映射操作的话,最终结果只能是[Stream<Integer>,Integer,Integer],所以我们需要使用flatMap将a进行扁平化从而得到一个Integer

public static void main(String[] args) {
        //生成a
        Stream<int[]> result = IntStream.rangeClosed(1, 100).boxed()
                //基于a的范围生成 a-100范围的b,并过滤出平方再开方后可以整除的b,构成数组
                .flatMap(a -> IntStream.rangeClosed(a, 100).filter(b -> Math.sqrt(a * a + b * b) % 1 == 0).boxed().map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)}))
                //取前3个
                .limit(3);

        //打印输出
        result.forEach(r -> System.out.println(r[0] + " " + r[1] + " " + r[2]));


    }

最终输出结果如下:

3 4 5
5 12 13
6 8 10

但是这种写法不够好,可以看到我们得到合适a和b时,还需要手动调用boxed将其还原为原始流,再用map映射为数组,这样实在太麻烦了。

还记得我们特化流还原为原始流的一个方法mapToxxx方法吗?如果我们希望将其转为数组,我们在得到a和b之后,直接调用mapToObj,代码一步到位:

    public static void main(String[] args) {
        //生成a
        Stream<int[]> result = IntStream.rangeClosed(1, 100).boxed()
                //基于a的范围生成 a-100范围的b,并过滤出平方再开方后可以整除的b,构成数组
                .flatMap(a -> IntStream.rangeClosed(a, 100).filter(b -> Math.sqrt(a * a + b * b) % 1 == 0).mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)}))
                //取前3个
                .limit(3);

        //打印输出
        result.forEach(r -> System.out.println(r[0] + " " + r[1] + " " + r[2]));


    }

以上就是浅聊java8中数值流的使用的详细内容,更多关于java8数值流的资料请关注脚本之家其它相关文章!

相关文章

  • 使用kafka如何选择分区数及kafka性能测试

    使用kafka如何选择分区数及kafka性能测试

    这篇文章主要介绍了使用kafka如何选择分区数及kafka性能测试,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java线程中断及线程中断的几种使用场景小结

    Java线程中断及线程中断的几种使用场景小结

    在并发编程中,合理使用线程中断机制可以提高程序的鲁棒性和可维护性,本文主要介绍了Java线程中断及线程中断的几种使用场景小结,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • SpringBoot项目创建单元测试的流程步骤

    SpringBoot项目创建单元测试的流程步骤

    在日常开发的过程中,对自己的代码进行单元测试是个非常重要的过程,一方面可以最小范围的针对一个方法进行测试,提高测试的简便性以及测试的成本,本篇文章主要是为了总结一下如何优雅的在Springboot项目中使用单元测试去测试功能,需要的朋友可以参考下
    2024-11-11
  • springboot接收excel数据文件去重方式

    springboot接收excel数据文件去重方式

    文章主要介绍了如何在Spring Boot中实现文件上传并入库的功能,包括读取Excel文件、生成Entity对象、使用MergeInto语句进行数据库操作以及注意事项
    2024-12-12
  • SpringBoot集成thymeleaf浏览器404的解决方案

    SpringBoot集成thymeleaf浏览器404的解决方案

    前后端不分离的古早 SpringMVC 项目通常会使用 thymeleaf 模板引擎来完成 html 页面与后端接口之间的交互,如果要将项目架构升级成 SpringBoot , thymeleaf 也可以照常集成,但有时候会踩到一些坑,所以本文给大家介绍了SpringBoot集成thymeleaf浏览器404的解决方案
    2024-12-12
  • ElasticSearch学习之多条件组合查询验证及示例分析

    ElasticSearch学习之多条件组合查询验证及示例分析

    这篇文章主要为大家介绍了ElasticSearch 多条件组合查询验证及示例分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • Java实现浏览器大文件上传的示例详解

    Java实现浏览器大文件上传的示例详解

    文件上传是许多项目都有的功能,用户上传小文件速度一般都很快,但如果是大文件几个g,几十个g的时候,上传了半天,马上就要完成的时候,网络波动一下,文件又要重新上传,所以本文给大家介绍了Java实现浏览器大文件上传的示例,需要的朋友可以参考下
    2024-07-07
  • Java中scheduleAtFixedRate的用法

    Java中scheduleAtFixedRate的用法

    如何正确使用Java中的scheduleAtFixedRate是一篇介绍Java中定时任务调度器的文章。它详细介绍了scheduleAtFixedRate方法的用法、参数及作用,并给出了一些实例以帮助读者更好地理解其使用。本文为Java开发人员提供了一些实用的技巧,帮助他们更好地管理和控制定时任务
    2023-04-04
  • 基于Servlet实现技术问答网站系统

    基于Servlet实现技术问答网站系统

    这篇文章主要为大家详细介绍了基于Servlet实现技术问答网站系统,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • Java导出Excel文件的方法

    Java导出Excel文件的方法

    项目里很多接口都涉及导出Excel文件,所以本文给大家总结了项目中导出Excel文件的方法,并通过代码示例讲解的非常详细,具有一定的参考价值,需要的朋友可以参考下
    2025-02-02

最新评论