文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

我们一起实战学习Java8 Stream新特性

2024-11-29 17:58

关注

实体类声明

@Getter
@Setter
public class ComputerDTO {
    
    private String computerNo;
    
    private String brand;
    
    private BigDecimal price;
    
    private Integer coreQuantity;
    
    private Integer memory;
    
    private String hardDisk;
    
    private String place;
}

场景描述

我暂且充当一下产品经理,现在罗列出了下列需求,基本上覆盖了日常使用Stream流的大多场景,各位小伙伴可以先行看一看有没有思路。

经典场景

  1. 筛选出所有品牌为“abc”的电脑,并按价格降序排序。
  2. 计算所有电脑的价格总和。
  3. 找出内存最大的电脑的信息。
  4. 统计硬盘类型为SSD的电脑数量。
  5. 将所有电脑的产地转换成一个不重复的集合。
  6. 创建一个Map,键为品牌,值为该品牌的电脑列表。
  7. 获取每个品牌的平均价格。
  8. 获取一个Map,键为计算机编号,值为该计算机信息。

组合应用

  1. 筛选出价格低于5000元且CPU核数大于等于4的电脑。
  2. 找出每个品牌中最贵的电脑,并返回一个包含这些电脑的列表。
  3. 统计每个品牌的电脑数量,并按数量降序排序。
  4. 找出所有品牌为“abc”且内存大于等于8GB的电脑,并按CPU核数降序排序。
  5. 统计每个品牌的平均价格,并找出平均价格最高的品牌。
  6. 创建一个Map,键为品牌,值为该品牌所有电脑的总价。

经典场景实战攻克

下面我来带大家一道一道攻克,并在这个过程中带大家梳理一下Stream流使用过程中的一些注意事项。

我们假设需要处理的数据是一个ComputerDTO的List,如下:

List computers=getComputers();

Stream流模型的操作很丰富,我们今天将使用到一些常用的方法,这些方法可以被分成两种。

终结方法:返回值类型不再是Stream类型的方法,不再支持链式调用。如count、forEach、collect方法等。

非终结方法:返回值类型仍然是Stream类型的方法,支持链式调用。如map、filter、sorted方法等。

场景1

筛选出所有品牌为“abc”的电脑,并按价格降序排序。

List abcComputers = computers.stream()
       .filter(computer -> "abc".equals(computer.getBrand()))
       .sorted(Comparator.comparing(ComputerDTO::getPrice).reversed())
       .collect(Collectors.toList());

首先我们将这个场景拆解成两个过程,第一个过程是将列表中的所有品牌不为“abc”的电脑过滤掉,这里我们需要使用到filter方法。

filter方法的入参是含一个参数返回结果为boolean类型的函数式接口,这里我们直接使用lambda表达式实现。

需要注意的是filter方法将会保留符合表达式的数据,这里可以和集合的removeIf方法进行对比记忆,并且我们使用stream处理数据并不会改变原集合computers。

第二个过程是将过滤后的结果按照价格降序排序,这里我们使用sorted方法实现。

sorted方法的入参是一个比较器Comparator,这里我们直接使用Comparator.comparing方法构建一个根据价格排序的比较器,并使用reversed方法返回一个降序的比较器。

最后我们使用终结方法collect(Collectors.toList())将结果收集到集合当中。

场景2

计算所有电脑的价格总和。

BigDecimal totalCost = computers.stream()
       .map(ComputerDTO::getPrice)
       .reduce(BigDecimal.ZERO, BigDecimal::add);

这个场景我们需要先将集合中的ComputerDTO对象转换为价格,因为我们需要的最终结果是一个BigDecimal类型,所以需要先使用map方法对数据进行转换。

map方法的入参是一个Function函数式接口,下面贴出一张图帮助大家理解map方法的作用。

图片

map方法在工作中常常被使用,例如需要根据一个实体类集合获取一个属性值集合,通常先使用map方法获取属性值,看情况需要可以使用distinct方法去重、filter过滤、sorted方法排序,最后使用collect方法收集起来。

在当前场景中我们需要计算所有电脑的价格总和,所以可以使用reduce终结方法进行汇总。

图片

场景3

找出内存最大的电脑的信息。

Optional maxMemoryComputer = computers.stream()
       .max(Comparator.comparingInt(ComputerDTO::getMemory));

这个场景简单粗暴,直接将待处理数据转成流,然后使用max方法就可以解决,不过需要注意的是max方法返回的数据使用Optional包了一层。

Optional类同样是Java8提供的,使用isPresent方法可以判断包含值是否为null,通过get方法可以获取包含值,如果包含值为null会抛出一个NoSuchElementException异常,所以通常搭配isPresent方法使用。

场景4

统计硬盘类型为SSD的电脑数量。

long ssdCount = computers.stream()
       .filter(computer -> computer.getHardDisk().contains("SSD"))
       .count();

这个场景使用了一个新的终结方法count,count方法用于统计流中元素个数,返回值类型为long类型。

场景5

将所有电脑的产地转换成一个不重复的集合。

Set places = computers.stream()
       .map(ComputerDTO::getPlace)
       .collect(Collectors.toSet());

这个场景在工作中常常会用到,也是上面提到的map的经典用法,只不过这里将流中数据通过collect(Collectors.toSet())收集到了Set中,利用了Set的特性进行去重,而没有使用distinct方法进行去重。

这里引申一下,上点难度,如果这里最终需要获取的是根据产地去重后的ComputerDTO集合呢,使用流的方式又该怎样实现。

这是工作中另外的一个经典场景,List集合按照对象属性去重,其实最终也是利用了Set的特性,在Set的构造函数中传入了自定义比较器!

List newList = computers.stream().collect(Collectors
                .collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(ComputerDTO::getPlace)))
                        , ArrayList::new));

这里使用的Collectors.collectingAndThen方法只是将返回结果Set转化为了List,核心处理就是Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(ComputerDTO::getPlace)))。

场景6

创建一个Map,键为品牌,值为该品牌的电脑列表。

Map> computersByBrand = computers.stream()
       .collect(Collectors.groupingBy(ComputerDTO::getBrand));

这个场景也是工作中常常会遇到的场景,对原有数据根据某一个纬度进行分组,然后不同组的数据使用不同的逻辑进行处理。Stream为这个需求也提供了专门的方法Collectors.groupingBy。

场景7

获取每个品牌的平均价格。

Map averagePrices = computers.stream()
       .collect(Collectors.groupingBy(ComputerDTO::getBrand, Collectors.averagingDouble(c -> c.getPrice().doubleValue())));

这个场景是场景6的进阶玩法,根据某一个纬度进行分组,分组后再对数据进行处理。

这里使用的是Collectors.groupingBy两个参数的重载方法。

场景8

获取一个Map,键为计算机编号,值为该计算机信息。

Map computerInfoMap = computers.stream().collect(Collectors.toMap(ComputerDTO::getComputerNo, item -> item));

Map computerInfoMap = computers.stream().collect(HashMap::new, (m, v) -> m.put(v.getComputerNo(), v), HashMap::putAll);

这个场景在工作中出现的频率很高,通常有两种方法去实现,其中Collectors.toMap方法有一个小坑,大家在使用时需要注意一下。

java8的Collectors.toMap的value不能为null。

如果待处理的数据中value值存在null,则会出现莫名其妙的空指针异常,所以我在工作中往往会使用第二种方式。

组合应用代码参考

通过上面经典场景的讲解,其实我们可以注意到,基本上绝大多数的应用都离不开collect方法,这个方法在流的使用中极为重要,在后续的文章中我也会为大家进一步的讲解collect方法,敬请期待!

组合场景就是对经典场景中的一些常用API进行组合应用,所以就不在这里一一赘述,仅为大家提供了参考代码。

  1. 筛选出价格低于5000元且CPU核数大于等于4的电脑。
List affordableAndPowerful = computers.stream()
       .filter(computer -> computer.getPrice().compareTo(new BigDecimal("5000")) < 0 && computer.getCoreQuantity() >= 4)
       .collect(Collectors.toList());
  1. 找出每个品牌中最贵的电脑,并返回一个包含这些电脑的列表。
Map mostExpensivePerBrand = computers.stream()
            .collect(Collectors.groupingBy(ComputerDTO::getBrand,
                    Collectors.collectingAndThen(
                            Collectors.maxBy(Comparator.comparing(ComputerDTO::getPrice)),
                            optional -> optional.orElseThrow(() -> new NoSuchElementException("No computers found for this brand"))
       )
   ));

    List mostExpensiveComputers = new ArrayList<>(mostExpensivePerBrand.values());
  1. 统计每个品牌的电脑数量,并按数量降序排序。
Map brandCounts = computers.stream()
       .collect(Collectors.groupingBy(ComputerDTO::getBrand, Collectors.counting()));

   List> sortedBrandCounts = brandCounts.entrySet().stream()
       .sorted(Map.Entry.comparingByValue().reversed())
       .collect(Collectors.toList());
  1. 找出所有品牌为“abc”且内存大于等于8GB的电脑,并按CPU核数降序排序。
List abcHighMemoryComputers = computers.stream()
       .filter(computer -> "abc".equals(computer.getBrand()) && computer.getMemory() >= 8)
       .sorted(Comparator.comparingInt(ComputerDTO::getCoreQuantity).reversed())
       .collect(Collectors.toList());
  1. 统计每个品牌的平均价格,并找出平均价格最高的品牌。
Optional> highestAveragePrice = computers.stream()
       .collect(Collectors.groupingBy(
           ComputerDTO::getBrand,
           Collectors.averagingDouble(c -> c.getPrice().doubleValue())
       ))
       .entrySet().stream()
       .max(Map.Entry.comparingByValue());

   String highestBrand = highestAveragePrice.map(Map.Entry::getKey).orElse(null);
   double highestAverage = highestAveragePrice.map(Map.Entry::getValue).orElse(0.0);
  1. 创建一个Map,键为品牌,值为该品牌所有电脑的总价。
Map totalPricesByBrand = computers.stream()
       .collect(Collectors.groupingBy(
           ComputerDTO::getBrand,
           Collectors.reducing(BigDecimal.ZERO, ComputerDTO::getPrice, BigDecimal::add)
       ));

结语

学会使用java8的Stream新特性,可以极大的减少工作中的代码量,可以使自己的代码看起来更整洁,同时很多框架源码中也大量使用Stream,掌握了它也可以为我们阅读源码提供帮助,希望这篇文章可以给大家带来帮助。

来源:Java极客技术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯