玩转 Java8 Stream,让你代码更高效紧凑简洁

简介: 玩转 Java8 Stream,让你代码更高效紧凑简洁

系列文章

序号 标题 文章链接
1 我使用 Lambda 表达式将上百行的代码精简到几行 https://blog.csdn.net/chenlixiao007/article/details/106366676
2 玩转 Java8 Optional,让你代码更紧凑简洁且不再出现空指针 https://blog.csdn.net/chenlixiao007/article/details/113358813


# 前言 > Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。
>新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。它可以让你以一种声明的方式处理数据,从而写出高效率、干净、简洁的代码。
>这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。 # 一、Stream 特性 1. 元素是特定类型的对象,形成一个队列。 Java中的Stream不会存储元素,而是按需计算按照特定的规则对数据进行计算,一般会输出结果。 2. Stream不会改变数据源,一般情况下会产生一个新的集合或者新值。 3. Stream流的来源,可以是集合,数组,I/O channel, 产生器generator 等等。 4. Stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 5. 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
Stream流的操作可以大概分为2种: 1. **中间操作**:每次操作都返回流对象本身。 2. **终端操作**:一个流只可以进行一次终端操作,即产生一个新的集合或者新值。终端操作结束后流无法再次使用。 # 二、Stream 创建 > 在 Java 8 中, Stream可以由集合或数组创建而来,生成的流有2种类型: > 1. **stream()** :串行流,由主线程按顺序对流执行操作。 > 2. **parallelStream()** :并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如计算集合中的数量之和。如果流种数据量很大,并行流可以加快处理速度。串行流可以通过`parallel() `方法把顺序流转换成并行流。 ## 2.1 用集合创建流 > 因为集合继承或实现了`java.util.Collection`接口,而Collection接口定义了`stream()`和`parallelStream()`方法,所以可通过集合的stream() 和parallelStream()方法创建流。 ```java // 创建集合 List list = Arrays.asList("张三", "李四", "王五"); // 创建一个串行流 Stream stream = list.stream(); // 创建一个并行流 Stream parallelStream = list.parallelStream(); ``` ## 2.2 用数组创建流 > 使用java.util.Arrays.stream(T[] array)方法用数组创建流。 ```java // 创建数组 String[] persons = {"张三", "李四", "王五"}; // 创建一个串行流 Stream stream = Arrays.stream(persons); // 创建一个并行流 Stream parallelStream = Arrays.stream(persons).parallel(); ``` ## 2.3 Stream静态方法 > 使用Stream的静态方法生成Stream,例如`of()`、`iterate()`、`generate()`等。 ```java Stream stream2 = Stream.of("张三", "李四", "王五"); // 输出5个奇数 1 3 5 7 9 Stream stream3 = Stream.iterate(1, x -> x + 2).limit(5); stream3.forEach(System.out::println); // 生成2个UUID Stream stream4 = Stream.generate(UUID::randomUUID).limit(2); stream4.forEach(System.out::println); ``` # 三、Stream 使用案例 > 以下所有案例会基于学生数据,学生类,以及测试数据如下: ```java package com.nobody; /** * @Description 学生类 * @Author Mr.nobody * @Date 2021/1/17 * @Version 1.0 */ public class Student { // 主键 private String id; // 姓名 private String name; // 年龄 private int age; // 性别 private String sex; // 成绩 private double score; public Student(String id, String name, int age, String sex, double score) { this.id = id; this.name = name; this.age = age; this.sex = sex; this.score = score; } // 省略get和set方法,toString方法,如若测试需自行添加 ``` ```java List students = new ArrayList<>(16); students.add(new Student("1", "张三", 18, "male", 88)); students.add(new Student("2", "李四", 17, "male", 60)); students.add(new Student("3", "王五", 18, "male", 100)); students.add(new Student("4", "赵六", 20, "male", 10)); students.add(new Student("5", "董七", 14, "female", 95)); students.add(new Student("6", "幺八", 21, "male", 55)); students.add(new Student("7", "老九", 20, "female", 66)); students.add(new Student("8", "小明", 18, "male", 100)); students.add(new Student("9", "小红", 22, "female", 95)); students.add(new Student("10", "小张", 25, "male", 90)); ``` ## 3.1 遍历 forEach ```java students.stream().forEach(System.out::println); // 输出结果 Student{id='1', name='张三', age=18, sex=male, score=88.0} Student{id='2', name='李四', age=17, sex=male, score=60.0} Student{id='3', name='王五', age=18, sex=male, score=100.0} Student{id='4', name='赵六', age=20, sex=male, score=10.0} Student{id='5', name='董七', age=14, sex=female, score=95.0} Student{id='6', name='幺八', age=21, sex=male, score=55.0} Student{id='7', name='老九', age=20, sex=female, score=66.0} Student{id='8', name='小明', age=18, sex=male, score=100.0} Student{id='9', name='小红', age=22, sex=female, score=95.0} Student{id='10', name='小张', age=25, sex=male, score=90.0} ``` ## 3.2 过滤 filter ```java // 过滤出成绩100分的学生 List students1 = students.stream().filter(student -> student.getScore() == 100).collect(Collectors.toList()); students1.forEach(System.out::println); // 输出结果 Student{id='3', name='王五', age=18, sex=male, score=100.0} Student{id='8', name='小明', age=18, sex=male, score=100.0} ``` ## 3.3 查找 findFirst,findAny > 一般filter和find搭配使用,即从过滤符合条件的数据中,获得一个数据。 ```java // 串行流,匹配第一个 Optional studentOptional = students.stream().filter(student -> student.getAge() >= 20).findFirst(); if (studentOptional.isPresent()) { Student student = studentOptional.get(); System.out.println(student); } // 上面输出语句可简写如下 // studentOptional.ifPresent(System.out::println); // 并行流,匹配任意一个,findAny一般用于并行流 Optional studentOptiona2 = students.parallelStream().filter(student -> student.getAge() >= 20).findAny(); studentOptiona2.ifPresent(System.out::println); // 输出结果 Student{id='4', name='赵六', age=20, sex=male, score=10.0} Student{id='7', name='老九', age=20, sex=female, score=66.0} ``` ## 3.4 匹配 match ```java // 是否存在100分的学生 boolean anyMatch = students.stream().anyMatch(student -> student.getScore() == 100); // 是否全部学生都100分 boolean allMatch = students.stream().allMatch(student -> student.getScore() == 100); // 是否全部学生都没有100分 boolean noneMatch = students.stream().noneMatch(student -> student.getScore() == 100); System.out.println(anyMatch); System.out.println(allMatch); System.out.println(noneMatch); // 输出结果 true false false ``` ## 3.5 映射 map > 映射,顾名思义,将一个对象映射成另外一个对象。即一个Stream流中的所有元素按照一定的映射规则,映射到另一个流中。映射有map和flatMap两种类型: > 1. **map**:接收一个函数作为参数,此函数作用到Stream中每一个元素,形成一个新的元素,所有新的元素组成一个新的流。 > 2. **flatMap**:接收一个函数作为参数,它将流中的每个元素都转换成另一个流,然后把所有流再连接形成一个最终流。 ```java // 获取每个学生的姓名 List studentNames = students.stream().map(Student::getName).collect(Collectors.toList()); System.out.println(studentNames); // 每个学生的成绩加10分 List studentScores = students.stream().map(student -> student.getScore() + 10) .collect(Collectors.toList()); System.out.println(studentScores); // 输出结果 [张三, 李四, 王五, 赵六, 董七, 幺八, 老九, 小明, 小红, 小张] [98.0, 70.0, 110.0, 20.0, 105.0, 65.0, 76.0, 110.0, 105.0, 100.0] ``` ```java List list = Arrays.asList("a-b-c-d", "g-h-i"); List list1 = list.stream().flatMap(s -> Arrays.stream(s.split("-"))).collect(Collectors.toList()); System.out.println(list1); // 输出结果 [a, b, c, d, g, h, i] ``` ## 3.6 截取流 limit ```java // limit方法用于获取指定数量的流。例如下面演示取出学习成绩大于70的5个人 List students2 = students.stream().filter(student -> student.getScore() > 70) .limit(5).collect(Collectors.toList()); students2.forEach(System.out::println); // 跳过第一个再取2个 List students8 = students.stream().skip(1).limit(2).collect(Collectors.toList()); // 获取5个int随机数,按从小到大排序 Random random = new Random(); random.ints().limit(5).sorted().forEach(System.out::println); // 输出结果 Student{id='1', name='张三', age=18, sex=male, score=88.0} Student{id='3', name='王五', age=18, sex=male, score=100.0} Student{id='5', name='董七', age=14, sex=female, score=95.0} Student{id='8', name='小明', age=18, sex=male, score=100.0} Student{id='9', name='小红', age=22, sex=female, score=95.0} -1490202714 145340547 368332155 388399398 1099579920 ``` ## 3.7 排序 sorted > sorted 方法用于对流中的元素进行排序,有两种排序: > 1. **sorted()**:自然排序,流中元素需要实现Comparable接口。 > 2. **sorted(Comparator<? super T> comparator)**:需要自定义排序器。 ```java // 按成绩升序 List students3 = students.stream().sorted(Comparator.comparing(Student::getScore)) .collect(Collectors.toList()); System.out.println("按成绩升序"); students3.forEach(System.out::println); // 按成绩降序 List students4 = students.stream().sorted(Comparator.comparing(Student::getScore).reversed()) .collect(Collectors.toList()); System.out.println("按成绩降序"); students4.forEach(System.out::println); // 按成绩升序,再按年龄升序 List students5 = students.stream() .sorted(Comparator.comparing(Student::getScore).thenComparing(Student::getAge)) .collect(Collectors.toList()); System.out.println("按成绩升序,再按年龄升序"); students5.forEach(System.out::println); // 按成绩升序,再按年龄降序 List students6 = students.stream().sorted((s1, s2) -> { if (s1.getScore() != s2.getScore()) { return (int) (s1.getScore() - s2.getScore()); } else { return (s2.getAge() - s1.getAge()); } }).collect(Collectors.toList()); System.out.println("按成绩升序,再按年龄降序"); students6.forEach(System.out::println); ``` ## 3.8 去重 distinct ```java List list2 = Arrays.asList("a", "b", "a", "c", "f", "e", "f"); List list3 = list2.stream().distinct().collect(Collectors.toList()); Set stringSet = list2.stream().collect(Collectors.toSet()); // list转set也可以达到去重效果 System.out.println(list3); System.out.println(stringSet); // 输出结果 [a, b, c, f, e] [a, b, c, e, f] ``` ## 3.9 统计 summaryStatistics > 一些收集器能产生统计结果,例如Collectors提供了一系列用于数据统计的静态方法,它们主要用于int、double、long等基本类型上。 ```java // 总和统计summaryStatistics DoubleSummaryStatistics doubleSummaryStatistics = students.stream().mapToDouble(Student::getScore).summaryStatistics(); System.out.println("平均值:" + doubleSummaryStatistics.getAverage()); System.out.println("总个数:" + doubleSummaryStatistics.getCount()); System.out.println("最大值:" + doubleSummaryStatistics.getMax()); System.out.println("最小值:" + doubleSummaryStatistics.getMin()); System.out.println("总和值:" + doubleSummaryStatistics.getSum()); // 输出结果 平均值:75.9 总个数:10 最大值:100.0 最小值:10.0 总和值:759.0 ``` ```java // 统计个数 long count = students.stream().count(); // 平均值 Double averageScore = students.stream().collect(Collectors.averagingDouble(Student::getScore)); // 最大值和最小值 Optional maxScore = students.stream().map(Student::getScore).max(Double::compare); Optional minScore = students.stream().map(Student::getScore).min(Double::compare); // 求和 double sumScore = students.stream().mapToDouble(Student::getScore).sum(); // 一次性统计所有 DoubleSummaryStatistics doubleSummaryStatistics1 = students.stream().collect(Collectors.summarizingDouble(Student::getScore)); System.out.println("单个维度计算:"); System.out.println("统计个数:" + count); System.out.println("平均值:" + averageScore); maxScore.ifPresent(aDouble -> System.out.println("最大值:" + aDouble)); minScore.ifPresent(aDouble -> System.out.println("最小值:" + aDouble)); System.out.println("求和:" + sumScore); System.out.println("一次性统计所有:" + doubleSummaryStatistics1); // 输出结果 单个维度计算: 统计个数:10 平均值:75.9 最大值:100.0 最小值:10.0 求和:759.0 ``` ## 3.10 归约 reduce > 归约,把一个流归约(缩减)成一个值,能实现对集合求和、求乘积和求最值等操作。 ```java List integerList = Arrays.asList(6, 7, 1, 10, 11, 7, 13, 20); // 求和 Optional sum1 = integerList.stream().reduce(Integer::sum); // 求和,基于10的基础上 Integer sum2 = integerList.stream().reduce(10, Integer::sum); // 求最大值 Optional max1 = integerList.stream().reduce((x, y) -> x > y ? x : y); // 求最大值,基于与50比较的基础上 Integer max2 = integerList.stream().reduce(50, Integer::max); Optional min = integerList.stream().reduce(Integer::min); // 求乘积 Optional product = integerList.stream().reduce((x, y) -> x * y); System.out.println("原始集合:" + integerList); System.out.println("集合求和:" + sum1.get() + "," + sum2); System.out.println("集合求最大值:" + max1.get() + "," + max2); System.out.println("集合求最小值:" + min.get()); System.out.println("集合求积:" + product.get()); // 输出结果 原始集合:[6, 7, 1, 10, 11, 7, 13, 20] 集合求和:75,85 集合求最大值:20,50 集合求最小值:1 集合求积:8408400 ``` ## 3.11 归集 toList,toSet,toMap > Java中的Stream不会存储元素,而是按需计算按照特定的规则对数据进行计算,一般会输出结果。所以流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。`比较常用的是toList、toSet和toMap`,以及复杂的toCollection、toConcurrentMap等。 ```java // 获取学生名字,形成新的list集合 List studentNames1 = students.stream().map(Student::getName).collect(Collectors.toList()); // 获取年龄大于等于15的年龄set集合 Set ageSet = students.stream().filter(student -> student.getAge() >= 15) .map(Student::getAge).collect(Collectors.toSet()); // 创建学生ID和学生实体的map Map studentMap = students.stream().collect(Collectors.toMap(Student::getId, student -> student)); System.out.println(studentNames1); System.out.println(ageSet); studentMap.forEach((key, value) -> System.out.println(key + ":" + value)); // 输出结果 [张三, 李四, 王五, 赵六, 董七, 幺八, 老九, 小明, 小红, 小张] [17, 18, 20, 21, 22, 25] 1:Student{id='1', name='张三', age=18, sex=male, score=88.0} 2:Student{id='2', name='李四', age=17, sex=male, score=60.0} 3:Student{id='3', name='王五', age=18, sex=male, score=100.0} 4:Student{id='4', name='赵六', age=20, sex=male, score=10.0} 5:Student{id='5', name='董七', age=14, sex=female, score=95.0} 6:Student{id='6', name='幺八', age=21, sex=male, score=55.0} 7:Student{id='7', name='老九', age=20, sex=female, score=66.0} 8:Student{id='8', name='小明', age=18, sex=male, score=100.0} 9:Student{id='9', name='小红', age=22, sex=female, score=95.0} 10:Student{id='10', name='小张', age=25, sex=male, score=90.0} ``` ## 3.12 分组 partitioningBy,groupingBy > **partitioningBy(分区)**:stream中的元素按条件被分为两个Map。 **groupingBy(分组)**:stream中的元素按条件被分为多个Map。 ```java // 按条件学生成绩是否大于等于60,划分为2个组 Map> studentScorePart = students.stream() .collect(Collectors.partitioningBy(student -> student.getScore() >= 60)); // 按性别分组 Map> studentSexMap = students.stream().collect(Collectors.groupingBy(Student::getSex)); // 按年龄分组 Map> studentAgeMap = students.stream().collect(Collectors.groupingBy(Student::getAge)); // 先按性别分组,再按年龄分组 Map>> collect = students.stream().collect( Collectors.groupingBy(Student::getSex, Collectors.groupingBy(Student::getAge))); System.out.println("按条件学生成绩是否大于等于60,划分为2个组:"); studentScorePart.forEach((aBoolean, students7) -> { System.out.println("成绩大于等于60?:" + aBoolean); students7.forEach(System.out::println); }); System.out.println("按性别分组:"); studentSexMap.forEach((sex, students7) -> { System.out.println("性别?:" + sex); students7.forEach(System.out::println); }); System.out.println("按年龄分组:"); studentAgeMap.forEach((age, students7) -> { System.out.println("年龄:" + age); students7.forEach(System.out::println); }); System.out.println("先按性别分组,再按年龄分组:"); collect.forEach((sex, integerListMap) -> { System.out.println("性别:" + sex); integerListMap.forEach((age, students7) -> { System.out.println("年龄:" + age); students7.forEach(System.out::println); }); }); //输出结果 按条件学生成绩是否大于等于60,划分为2个组: 成绩大于等于60?:false Student{id='4', name='赵六', age=20, sex=male, score=10.0} Student{id='6', name='幺八', age=21, sex=male, score=55.0} 成绩大于等于60?:true Student{id='1', name='张三', age=18, sex=male, score=88.0} Student{id='2', name='李四', age=17, sex=male, score=60.0} Student{id='3', name='王五', age=18, sex=male, score=100.0} Student{id='5', name='董七', age=14, sex=female, score=95.0} Student{id='7', name='老九', age=20, sex=female, score=66.0} Student{id='8', name='小明', age=18, sex=male, score=100.0} Student{id='9', name='小红', age=22, sex=female, score=95.0} Student{id='10', name='小张', age=25, sex=male, score=90.0} 按性别分组: 性别?:female Student{id='5', name='董七', age=14, sex=female, score=95.0} Student{id='7', name='老九', age=20, sex=female, score=66.0} Student{id='9', name='小红', age=22, sex=female, score=95.0} 性别?:male Student{id='1', name='张三', age=18, sex=male, score=88.0} Student{id='2', name='李四', age=17, sex=male, score=60.0} Student{id='3', name='王五', age=18, sex=male, score=100.0} Student{id='4', name='赵六', age=20, sex=male, score=10.0} Student{id='6', name='幺八', age=21, sex=male, score=55.0} Student{id='8', name='小明', age=18, sex=male, score=100.0} Student{id='10', name='小张', age=25, sex=male, score=90.0} 按年龄分组: 年龄:17 Student{id='2', name='李四', age=17, sex=male, score=60.0} 年龄:18 Student{id='1', name='张三', age=18, sex=male, score=88.0} Student{id='3', name='王五', age=18, sex=male, score=100.0} Student{id='8', name='小明', age=18, sex=male, score=100.0} 年龄:20 Student{id='4', name='赵六', age=20, sex=male, score=10.0} Student{id='7', name='老九', age=20, sex=female, score=66.0} 年龄:21 Student{id='6', name='幺八', age=21, sex=male, score=55.0} 年龄:22 Student{id='9', name='小红', age=22, sex=female, score=95.0} 年龄:25 Student{id='10', name='小张', age=25, sex=male, score=90.0} 年龄:14 Student{id='5', name='董七', age=14, sex=female, score=95.0} 先按性别分组,再按年龄分组: 性别:female 年龄:20 Student{id='7', name='老九', age=20, sex=female, score=66.0} 年龄:22 Student{id='9', name='小红', age=22, sex=female, score=95.0} 年龄:14 Student{id='5', name='董七', age=14, sex=female, score=95.0} 性别:male 年龄:17 Student{id='2', name='李四', age=17, sex=male, score=60.0} 年龄:18 Student{id='1', name='张三', age=18, sex=male, score=88.0} Student{id='3', name='王五', age=18, sex=male, score=100.0} Student{id='8', name='小明', age=18, sex=male, score=100.0} 年龄:20 Student{id='4', name='赵六', age=20, sex=male, score=10.0} 年龄:21 Student{id='6', name='幺八', age=21, sex=male, score=55.0} 年龄:25 Student{id='10', name='小张', age=25, sex=male, score=90.0} ``` ## 3.13 合并 joining > 将stream中的元素用指定的连接符(没有的话,则直接连接)连接成一个字符串。 ```java String joinName = students.stream().map(Student::getName).collect(Collectors.joining(", ")); System.out.println(joinName); // 输出结果 张三, 李四, 王五, 赵六, 董七, 幺八, 老九, 小明, 小红, 小张 ``` # 四、演示代码获取 > 此演示代码程序已放到Github:[https://github.com/LucioChn/java8-stream-demo](https://github.com/LucioChn/java8-stream-demo)
> 欢迎Star,给个支持!
相关文章
|
7天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
17 5
Java反射机制:解锁代码的无限可能
|
3天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
20 3
|
8天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
34 10
|
4天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
2天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
7天前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
10天前
|
搜索推荐 Java 数据库连接
Java|在 IDEA 里自动生成 MyBatis 模板代码
基于 MyBatis 开发的项目,新增数据库表以后,总是需要编写对应的 Entity、Mapper 和 Service 等等 Class 的代码,这些都是重复的工作,我们可以想一些办法来自动生成这些代码。
20 6
|
11天前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
Java
Java8中stream流处理数据21个小案例(学习使用)
Java8中stream流处理数据21个小案例(学习使用)
96 0
|
SQL 存储 前端开发
【Java技术指南】「Java8技术盲区」在奔向Java13的同时,也让我们仔细研究一下Stream的学习认知!
【Java技术指南】「Java8技术盲区」在奔向Java13的同时,也让我们仔细研究一下Stream的学习认知!
140 0
【Java技术指南】「Java8技术盲区」在奔向Java13的同时,也让我们仔细研究一下Stream的学习认知!