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

开发 前端
sorted方法的入参是一个比较器Comparator,这里我们直接使用Comparator.comparing方法构建一个根据价格排序的比较器,并使用reversed方法返回一个降序的比较器。最后我们使用终结方法collect(Collectors.toList())将结果收集到集合当中。

引言

大家好,我是了不起。刚刚不久Java23如期发布,但目前国内市场Java8还是占据着主导地位。今天我将模拟实际工作中的需求,带领大家学习Java8中的Stream新特性,大家可以收藏起来以防在需要的时候找不到。

实体类声明

@Getter
@Setter
public class ComputerDTO {
    /**
     * 计算机编号
     */
    private String computerNo;
    /**
     * 品牌
     */
    private String brand;
    /**
     * 价格
     */
    private BigDecimal price;
    /**
     * cpu核数
     */
    private Integer coreQuantity;
    /**
     * 内存GB
     */
    private Integer memory;
    /**
     * 硬盘信息,包含容量和类型,如 "500GB HDD" 或 "256GB SSD"
     */
    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<ComputerDTO> computers=getComputers();

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

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

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

场景1

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

List<ComputerDTO> 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<ComputerDTO> 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<String> places = computers.stream()
       .map(ComputerDTO::getPlace)
       .collect(Collectors.toSet());

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

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

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

List<ComputerDTO> 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<String, List<ComputerDTO>> computersByBrand = computers.stream()
       .collect(Collectors.groupingBy(ComputerDTO::getBrand));

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

场景7

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

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

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

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

场景8

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

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

Map<String, ComputerDTO> 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<ComputerDTO> affordableAndPowerful = computers.stream()
       .filter(computer -> computer.getPrice().compareTo(new BigDecimal("5000")) < 0 && computer.getCoreQuantity() >= 4)
       .collect(Collectors.toList());
  1. 找出每个品牌中最贵的电脑,并返回一个包含这些电脑的列表。
Map<String, ComputerDTO> 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<ComputerDTO> mostExpensiveComputers = new ArrayList<>(mostExpensivePerBrand.values());
  1. 统计每个品牌的电脑数量,并按数量降序排序。
Map<String, Long> brandCounts = computers.stream()
       .collect(Collectors.groupingBy(ComputerDTO::getBrand, Collectors.counting()));

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

结语

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

责任编辑:武晓燕 来源: Java极客技术
相关推荐

2021-05-19 10:37:16

WebFlux 前置工具

2022-02-14 10:16:22

Axios接口HTTP

2022-12-09 07:48:10

Java8Stream表达式

2022-12-30 09:24:23

Java8Stream操作

2023-03-28 07:32:37

2023-03-26 12:45:52

Linux内核头文件

2021-11-26 09:44:42

链表节点定位

2021-05-20 07:15:34

RSA-PSS算法签名

2022-12-01 09:59:57

内核观测性方法

2022-07-29 08:17:46

Java对象内存

2023-09-29 08:58:38

2023-09-15 06:56:01

RC.NET 6Release

2021-10-11 10:25:33

排列nums数组

2021-03-18 00:04:13

C# 类型数据

2022-12-06 08:12:11

Java关键字

2024-05-28 00:00:03

Java垃圾收集机制

2023-11-10 08:04:43

Java 17Java 11JDK

2024-02-20 21:34:16

循环GolangGo

2021-08-27 07:06:10

IOJava抽象

2022-01-17 06:59:40

Grep指令linux
点赞
收藏

51CTO技术栈公众号