浅聊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实现的统计字符算法示例

    java实现的统计字符算法示例

    这篇文章主要介绍了java实现的统计字符算法,涉及java针对字符的遍历、判断、运算等相关操作技巧,需要的朋友可以参考下
    2017-10-10
  • springboot2.0 @Slf4j log 彩色日志配置输出到文件

    springboot2.0 @Slf4j log 彩色日志配置输出到文件

    这篇文章主要介绍了springboot2.0 @Slf4j log日志配置输出到文件(彩色日志),解决方式是使用了springboot原生自带的一个log框架,结合实例代码给大家讲解的非常详细,需要的朋友可以参考下
    2023-08-08
  • Java反射机制详解

    Java反射机制详解

    这篇文章主要介绍了Java反射机制,首先简单介绍了反射机制的预备知识,进一步分析了Java反射机制的原理、实现技巧与应用方法,需要的朋友可以参考下
    2015-12-12
  • Java实体类实现链式操作实例解析

    Java实体类实现链式操作实例解析

    这篇文章主要介绍了Java实体类实现链式操作实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • quartz时间表达式Cron详解

    quartz时间表达式Cron详解

    这篇文章介绍了quartz时间表达式Cron,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • SpringBoot+RabbitMQ实现消息可靠传输详解

    SpringBoot+RabbitMQ实现消息可靠传输详解

    消息的可靠传输是面试必问的问题之一,保证消息的可靠传输主要在生产端开启 comfirm 模式,RabbitMQ 开启持久化,消费端关闭自动 ack 模式。本文将详解SpringBoot整合RabbitMQ如何实现消息可靠传输,需要的可以参考一下
    2022-05-05
  • Java中由substring方法引发的内存泄漏详解

    Java中由substring方法引发的内存泄漏详解

    这篇文章主要介绍了Java中由substring方法引发的内存泄漏详解,涉及substring方法引发的内存泄漏简介,substring的作用和实现原理等相关内容,具有一定借鉴价值,需要的朋友可以参考下
    2017-12-12
  • DoytoQuery 聚合查询方案示例详解

    DoytoQuery 聚合查询方案示例详解

    这篇文章主要为大家介绍了DoytoQuery 聚合查询方案示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • Java太阳系小游戏分析和源码详解

    Java太阳系小游戏分析和源码详解

    这篇文章主要对Java太阳系小游戏进行深入分析以及对应源码解释,进一步巩固了面向对象的知识,需要的朋友可以参考下
    2015-08-08
  • JSON复杂数据处理之Json树形结构数据转Java对象并存储到数据库的实现

    JSON复杂数据处理之Json树形结构数据转Java对象并存储到数据库的实现

    这篇文章主要介绍了JSON复杂数据处理之Json树形结构数据转Java对象并存储到数据库的实现的相关资料,需要的朋友可以参考下
    2016-03-03

最新评论