浅聊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数值流的资料请关注脚本之家其它相关文章!

相关文章

  • 一文讲解如何解决Java中的IllegalArgumentException异常

    一文讲解如何解决Java中的IllegalArgumentException异常

    这篇文章主要给大家介绍了关于如何解决Java中IllegalArgumentException异常的相关资料,IllegalArgumentException是Java中的一个标准异常类,通常在方法接收到一个不合法的参数时抛出,需要的朋友可以参考下
    2024-03-03
  • Mybatis批量插入更新xml方式和注解方式的方法实例

    Mybatis批量插入更新xml方式和注解方式的方法实例

    这篇文章主要给大家介绍了关于Mybatis批量插入更新xml方式和注解方式的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Mybatis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-12-12
  • Maven配置多仓库无效的解决

    Maven配置多仓库无效的解决

    在项目中使用Maven管理jar包依赖往往会出现很多问题,所以这时候就需要配置Maven多仓库,本文介绍了如何配置以及问题的解决
    2021-05-05
  • java使用itext如何直接生成pdf

    java使用itext如何直接生成pdf

    在工作中,制作PDF文件是常见需求,尤其是需要插入动态数据或图像时,使用PDF模板填充表单域通常足够,但对于复杂文件,可以通过拼接PDF内容来灵活排版,iText库提供了丰富的PDF操作功能,如设置页面大小、边距、字体、生成动态表格、添加水印、设置密码等
    2024-09-09
  • Java 超详细讲解异常的处理

    Java 超详细讲解异常的处理

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等
    2022-04-04
  • Java子类对象的实例化过程分析

    Java子类对象的实例化过程分析

    这篇文章主要介绍了Java子类对象的实例化过程,结合具体实例形式分析了java子类对象的实例化的步骤、原理、实现方法,需要的朋友可以参考下
    2019-09-09
  • java并发编程专题(一)----线程基础知识

    java并发编程专题(一)----线程基础知识

    这篇文章主要介绍了java并发编程线程的基础知识,文中讲解非常详细,帮助大家更好的学习JAVA并发编程,感兴趣想学习JAVA的可以了解下
    2020-06-06
  • Java编程Retry重试机制实例详解

    Java编程Retry重试机制实例详解

    这篇文章主要介绍了Java编程Retry重试机制实例详解,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • java使用异或对文件进行加密解密

    java使用异或对文件进行加密解密

    这篇文章主要为大家详细介绍了java使用异或方式对文件进行加密解密,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-03-03
  • Java中常用的9种文件下载方法总结

    Java中常用的9种文件下载方法总结

    下载文件在我们项目很常见,有下载视频、文件、图片、附件、导出Excel等,所以本文为大家整理了9中Java中常用的文件下载方式,希望对大家有所帮助
    2023-09-09

最新评论