【算法训练营 · 专项练习篇】Stream流与函数式编程
文章目录
- 函数式编程理论基础
- 函数式编程实践题(12道)
- 题目1:用Predicate组合过滤数字
- 题目2:用Function组合转换数据
- 题目3:用Consumer消费并处理数据
- 题目4:用Supplier生成随机数列表
- 题目5:静态方法引用转换字符串为整数
- 题目6:实例方法引用(对象)处理字符串
- 题目7:类的实例方法引用排序字符串
- 题目8:构造方法引用创建对象列表
- 题目9:自定义函数式接口实现加减乘除
- 题目10:用Comparator排序对象(多条件)
- 题目11:用lambda作为Runnable创建线程
- 题目12:用Optional结合Function处理可能为null的值
- Stream理论基础
- 附:关于原始类型流
- Stream编程实践题
- 题目1:过滤偶数并求平方和
- 题目2:字符串处理与排序
- 题目3:对象分组统计
- 题目4:求列表最大值
- 题目5:嵌套列表去重合并
- 题目6:字符串拼接
- 题目7:统计可被3整除的数字
- 题目8:筛选成绩前3名学生
- 题目9:计算列表元素乘积
- 题目10:数字分区(偶数/奇数)
- 题目11:对象属性拼接
- 题目12:使用Function接口转换
- 题目13:计算正整数平均值
函数式编程理论基础
关于Stream流与函数式编程的详细理论知识,可以参考我的另一篇文章:【Java笔记】函数式编程,本篇文章旨在通过练习题的方式,加速实践中对Stream流与函数式编程的掌握程度。
这里只提几个注意点:
1. 函数式编程的核心思想
- 函数是一等公民:函数可以作为参数传递、作为返回值返回,也可以赋值给变量。
- 不可变性:数据一旦创建不可修改(如Stream操作不会改变源数据),避免副作用。
- 纯函数:输入相同则输出一定相同,无对外界的依赖或修改(无副作用)。
2. Lambda表达式(Lambda表达式就是一个函数式接口的实例!)
- 语法:
(参数列表) -> { 代码块 }- 若参数只有一个,可省略括号:
s -> s.length() - 若代码块只有一行,可省略大括号和
return:(a, b) -> a + b
- 若参数只有一个,可省略括号:
以下是Java中常用函数式接口的汇总表格,涵盖核心接口的定义、功能及使用场景,方便快速查阅和实践:
- 函数式接口的核心是仅包含一个抽象方法(可含默认方法/静态方法),因此可通过Lambda表达式或方法引用简化实现。
- 子接口(如
UnaryOperator继承Function)的功能更具体,适用于输入输出类型一致的场景,可减少代码冗余。 Comparator虽不在java.util.function包中,但因高频用于函数式编程(如Stream.sorted()),故纳入表格。
| 接口名称 | 所在包 | 抽象方法 | 功能描述 | 示例代码(Lambda/方法引用) | 常见用途 |
|---|---|---|---|---|---|
Predicate<T> | java.util.function | boolean test(T t) | 接收一个参数,返回布尔值,用于条件判断 | n -> n % 2 == 0(判断偶数)String::isEmpty | 集合过滤(filter())、参数校验 |
Function<T, R> | java.util.function | R apply(T t) | 接收一个参数T,返回结果R,用于数据转换 | s -> s.length()(字符串转长度)Integer::parseInt | 集合转换(map())、数据格式转换 |
Consumer<T> | java.util.function | void accept(T t) | 接收一个参数,无返回值,用于消费数据(仅处理不返回) | System.out::println(打印元素)list::add | 遍历处理(forEach())、资源消耗 |
Supplier<T> | java.util.function | T get() | 无参数,返回T类型结果,用于提供数据(生成/获取数据) | () -> new ArrayList<>()Random::nextInt | 数据生成(Stream.generate())、延迟加载 |
Comparator<T> | java.util | int compare(T o1, T o2) | 接收两个参数,返回int(正负/0),用于比较排序 | (a, b) -> a - b(整数升序)String::compareTo | 集合排序(sorted())、自定义排序规则 |
UnaryOperator<T> | java.util.function | T apply(T t) | Function的子接口,输入和输出类型相同,用于一元运算 | x -> x * 2(翻倍)String::toUpperCase | 同类型数据转换(如自增、格式化) |
BinaryOperator<T> | java.util.function | T apply(T t1, T t2) | BiFunction的子接口,输入两个同类型参数,返回同类型结果,用于二元运算 | (a, b) -> a + b(求和)Math::max | 聚合操作(reduce())、两数运算 |
BiPredicate<T, U> | java.util.function | boolean test(T t, U u) | 接收两个参数,返回布尔值,用于多条件判断 | (a, b) -> a.contains(b)(字符串包含) | 多参数过滤、复合条件校验 |
BiFunction<T, U, R> | java.util.function | R apply(T t, U u) | 接收两个参数(T和U),返回R,用于多参数转换 | (a, b) -> a + b(拼接字符串)(k, v) -> new Entry(k, v) | 多字段组合转换、键值对处理 |
BiConsumer<T, U> | java.util.function | void accept(T t, U u) | 接收两个参数,无返回值,用于消费两个数据 | (k, v) -> System.out.println(k + ":" + v) | 遍历Map(forEach())、多参数处理 |
3. 方法引用(简化Lambda的“语法糖”)
当Lambda体仅调用一个已存在的方法时,可用方法引用替代,格式为类名/对象名::方法名。
- 4种类型:
- 静态方法引用:
类名::静态方法(如Integer::parseInt) - 实例方法引用(对象):
对象::实例方法(如str::toUpperCase,str是某个String对象) - 实例方法引用(类):
类名::实例方法(第一个参数是方法的调用者,如String::compareTo,等价于(s1, s2) -> s1.compareTo(s2)) - 构造方法引用:
类名::new(如ArrayList::new,等价于() -> new ArrayList<>())
- 静态方法引用:
4. 函数式接口的组合与扩展
- 默认方法:函数式接口可通过默认方法提供组合能力(如
Predicate.and()、Function.andThen())。
例:Predicate<Integer> isEven = n -> n%2==0;
Predicate<Integer> isGreaterThan5 = n -> n>5;
Predicate<Integer> combined = isEven.and(isGreaterThan5);(同时满足两个条件)
函数式编程实践题(12道)
题目1:用Predicate组合过滤数字
需求:给定整数列表,过滤出“大于5且为偶数”的数字,使用Predicate.and()组合条件。
知识点:Predicate接口、and()组合、lambda表达式
答案:
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;public class PredicateCombine {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(3, 6, 8, 10, 4, 12, 7);// 定义两个PredicatePredicate<Integer> isGreaterThan5 = n -> n > 5;Predicate<Integer> isEven = n -> n % 2 == 0;// 组合条件:大于5 且 是偶数List<Integer> result = numbers.stream().filter(isGreaterThan5.and(isEven)) // 组合Predicate.collect(Collectors.toList());System.out.println(result); // 输出:[6, 8, 10, 12]}
}
题目2:用Function组合转换数据
需求:给定字符串列表,先将字符串转为长度(Function<String, Integer>),再将长度翻倍(Function<Integer, Integer>),用andThen()组合两个Function。
知识点:Function接口、andThen()组合
答案:
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;public class FunctionCombine {public static void main(String[] args) {List<String> words = Arrays.asList("apple", "cat", "banana");// 第一个Function:字符串 -> 长度Function<String, Integer> strToLen = s -> s.length();// 第二个Function:长度 -> 翻倍Function<Integer, Integer> lenToDouble = len -> len * 2;// 组合:先转长度,再翻倍Function<String, Integer> combined = strToLen.andThen(lenToDouble);List<Integer> result = words.stream().map(combined) // 应用组合后的Function.collect(Collectors.toList());System.out.println(result); // 输出:[10, 6, 12]("apple"长度5→10,以此类推)}
}
题目3:用Consumer消费并处理数据
需求:给定整数列表,先用Consumer打印原始数字,再用Consumer计算平方并打印,用andThen()组合两个Consumer。
知识点:Consumer接口、andThen()组合
答案:
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;public class ConsumerCombine {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(2, 3, 4);// 第一个Consumer:打印原始数字Consumer<Integer> printOriginal = n -> System.out.print("原始:" + n + ";");// 第二个Consumer:打印平方Consumer<Integer> printSquare = n -> System.out.println("平方:" + n*n);// 组合:先打印原始,再打印平方Consumer<Integer> combined = printOriginal.andThen(printSquare);numbers.forEach(combined); // 遍历并应用组合后的Consumer// 输出:// 原始:2;平方:4// 原始:3;平方:9// 原始:4;平方:16}
}
题目4:用Supplier生成随机数列表
需求:用Supplier生成5个1-100的随机整数,收集为列表。
知识点:Supplier接口、generate()生成Stream
答案:
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;public class SupplierDemo {public static void main(String[] args) {Random random = new Random();// Supplier:生成1-100的随机数Supplier<Integer> randomNumSupplier = () -> random.nextInt(100) + 1;// 生成5个随机数并收集为列表List<Integer> randomNumbers = Stream.generate(randomNumSupplier).limit(5) // 限制数量.collect(Collectors.toList());System.out.println("随机数列表:" + randomNumbers); // 例:[35, 72, 18, 91, 5]}
}
题目5:静态方法引用转换字符串为整数
需求:给定字符串列表(如["123", "45", "6"]),用Integer.parseInt的静态方法引用转换为整数列表。
知识点:静态方法引用(类名::静态方法)
答案:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class StaticMethodReference {public static void main(String[] args) {List<String> strNumbers = Arrays.asList("123", "45", "6", "78");// 静态方法引用:Integer::parseInt 等价于 s -> Integer.parseInt(s)List<Integer> numbers = strNumbers.stream().map(Integer::parseInt) // 用静态方法引用转换.collect(Collectors.toList());System.out.println(numbers); // 输出:[123, 45, 6, 78]}
}
题目6:实例方法引用(对象)处理字符串
需求:给定字符串列表,用某个String对象的contains方法(实例方法)过滤出包含特定字符的字符串(如包含"a")。
知识点:实例方法引用(对象::方法)
答案:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class InstanceMethodRefObject {public static void main(String[] args) {List<String> words = Arrays.asList("apple", "banana", "cat", "date");String target = "a"; // 要包含的字符// 实例方法引用:target::contains 等价于 s -> target.contains(s)List<String> result = words.stream().filter(target::contains) // 过滤包含"a"的字符串.collect(Collectors.toList());System.out.println(result); // 输出:[]}
}
题目7:类的实例方法引用排序字符串
需求:用String::compareToIgnoreCase(类的实例方法引用)对字符串列表进行忽略大小写排序。
知识点:类的实例方法引用(类名::方法,第一个参数是调用者)
答案:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class InstanceMethodRefClass {public static void main(String[] args) {List<String> words = Arrays.asList("Banana", "apple", "Cherry", "date");// 类的实例方法引用:String::compareToIgnoreCase 等价于 (s1, s2) -> s1.compareToIgnoreCase(s2)List<String> sorted = words.stream().sorted(String::compareToIgnoreCase) // 忽略大小写排序.collect(Collectors.toList());System.out.println(sorted); // 输出:[apple, Banana, Cherry, date]}
}
题目8:构造方法引用创建对象列表
需求:给定字符串列表(如["张三", "李四"]),用Person::new(构造方法引用)创建Person对象列表(Person有name属性的构造方法)。
知识点:构造方法引用(类名::new)
答案:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;class Person {private String name;public Person(String name) { // 带name的构造方法this.name = name;}@Overridepublic String toString() {return "Person{name='" + name + "'}";}
}public class ConstructorRef {public static void main(String[] args) {List<String> names = Arrays.asList("张三", "李四", "王五");// 构造方法引用:Person::new 等价于 name -> new Person(name)List<Person> people = names.stream().map(Person::new) // 用构造方法创建对象.collect(Collectors.toList());System.out.println(people); // 输出:[Person{name='张三'}, Person{name='李四'}, Person{name='王五'}]}
}
题目9:自定义函数式接口实现加减乘除
需求:定义函数式接口Calculator(含抽象方法int calculate(int a, int b)),用lambda实现加、减、乘、除四种运算。
知识点:自定义函数式接口、lambda表达式实现
答案:
// 自定义函数式接口(只有一个抽象方法)
@FunctionalInterface
interface Calculator {int calculate(int a, int b);
}public class CustomFunctionalInterface {public static void main(String[] args) {// 实现加法Calculator add = (a, b) -> a + b;// 实现减法Calculator subtract = (a, b) -> a - b;// 实现乘法Calculator multiply = (a, b) -> a * b;// 实现除法(简单处理,不考虑0)Calculator divide = (a, b) -> a / b;System.out.println("3+5=" + add.calculate(3, 5)); // 8System.out.println("10-4=" + subtract.calculate(10, 4)); // 6System.out.println("6*7=" + multiply.calculate(6, 7)); // 42System.out.println("20/5=" + divide.calculate(20, 5)); // 4}
}
题目10:用Comparator排序对象(多条件)
需求:定义Student类(name、age、score),对学生列表先按score降序排序,若分数相同则按age升序排序,用lambda实现Comparator。
知识点:Comparator接口、thenComparing()组合排序
答案:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;class Student {private String name;private int age;private int score;public Student(String name, int age, int score) {this.name = name;this.age = age;this.score = score;}public int getScore() { return score; }public int getAge() { return age; }@Overridepublic String toString() {return name + "(age:" + age + ", score:" + score + ")";}
}public class ComparatorDemo {public static void main(String[] args) {List<Student> students = Arrays.asList(new Student("张三", 18, 90),new Student("李四", 19, 85),new Student("王五", 17, 90), // 与张三分数相同,按年龄升序new Student("赵六", 20, 88));// 排序逻辑:先按score降序,再按age升序Comparator<Student> byScoreDesc = (s1, s2) -> Integer.compare(s2.getScore(), s1.getScore());Comparator<Student> byAgeAsc = Comparator.comparingInt(Student::getAge);List<Student> sorted = students.stream().sorted(byScoreDesc.thenComparing(byAgeAsc)) // 组合排序.toList();System.out.println(sorted); // 输出:// [王五(age:17, score:90), 张三(age:18, score:90), 赵六(age:20, score:88), 李四(age:19, score:85)]}
}
题目11:用lambda作为Runnable创建线程
需求:用lambda表达式实现Runnable接口,创建两个线程分别打印“线程1运行中”和“线程2运行中”。
知识点:lambda实现Runnable(函数式接口)
答案:
public class LambdaRunnable {public static void main(String[] args) {// lambda实现Runnable接口的run()方法Runnable task1 = () -> System.out.println("线程1运行中");Runnable task2 = () -> System.out.println("线程2运行中");// 创建并启动线程new Thread(task1).start();new Thread(task2).start();// 输出(顺序可能交替):// 线程1运行中// 线程2运行中}
}
题目12:用Optional结合Function处理可能为null的值
需求:给定可能为null的字符串,若不为null则转换为大写并返回;若为null则返回默认值“EMPTY”。用Optional和Function实现。
知识点:Optional、map()(接受Function)、orElse()
答案:
import java.util.Optional;
import java.util.function.Function;public class OptionalWithFunction {public static void main(String[] args) {String str1 = "hello";String str2 = null;// Function:字符串 -> 大写Function<String, String> toUpper = String::toUpperCase;// 处理str1:不为null则转大写String result1 = Optional.ofNullable(str1).map(toUpper) // 应用Function.orElse("EMPTY");// 处理str2:为null则返回默认值String result2 = Optional.ofNullable(str2).map(toUpper).orElse("EMPTY");System.out.println(result1); // HELLOSystem.out.println(result2); // EMPTY}
}
Stream理论基础
关键特性说明:
- 创建操作:负责生成Stream,是流处理的起点,可从集合、数组、值、文件等多种来源生成。
- 中间操作:具有惰性执行特性(仅记录操作,不实际处理数据),且支持链式调用(返回Stream),最终需通过终止操作触发执行。
- 终止操作:触发流的实际处理(执行所有中间操作),返回具体结果(如集合、数值、布尔值等),且流一旦终止即不可再使用(否则抛出异常)。
一、Stream创建操作(生成Stream的方式)
| 操作类型 | 方法示例 | 功能描述 | 适用场景 | 示例代码 |
|---|---|---|---|---|
| 集合创建 | Collection.stream() | 从List/Set等Collection集合生成顺序流 | 处理集合数据 | List<Integer> list = Arrays.asList(1,2,3); Stream<Integer> stream = list.stream(); |
| 集合创建(并行) | Collection.parallelStream() | 从集合生成并行流(多线程处理,适合大数据量) | 高效处理大量数据 | list.parallelStream().forEach(System.out::println); |
| 数组创建 | Arrays.stream(T[] array) | 从数组生成Stream | 处理数组数据 | int[] arr = {1,2,3}; IntStream stream = Arrays.stream(arr); |
| 值序列创建 | Stream.of(T... values) | 直接通过多个值生成Stream(本质是数组转换) | 少量已知值的流处理 | Stream<String> stream = Stream.of("a", "b", "c"); |
| 空流创建 | Stream.empty() | 创建空Stream(避免null引发的空指针) | 作为默认返回值(如方法返回空流) | Stream<String> emptyStream = Stream.empty(); |
| 无限流(生成) | Stream.generate(Supplier<T>) | 通过Supplier生成无限长度的Stream(需配合limit限制长度) | 生成随机数、常量等无规律序列 | Stream<Integer> randoms = Stream.generate(() -> new Random().nextInt(10)).limit(5); |
| 无限流(迭代) | Stream.iterate(T seed, UnaryOperator<T>) | 从初始值seed开始,通过UnaryOperator迭代生成无限流(需配合limit) | 生成有规律的序列(如自增序列) | Stream<Integer> nums = Stream.iterate(1, n -> n + 2).limit(3); // 1,3,5 |
| 文件流创建 | Files.lines(Path path) | 从文件路径生成Stream(每行作为一个元素) | 读取文件内容 | Stream<String> lines = Files.lines(Paths.get("test.txt")); |
Stream中间操作(惰性执行,返回Stream,需终止操作触发)
| 操作类型 | 方法示例 | 功能描述 | 关键参数/返回值 | 示例代码(基于List<Integer> list = Arrays.asList(1,2,3,4,5,6);) |
|---|---|---|---|---|
| 过滤 | filter(Predicate<T> predicate) | 保留满足Predicate条件的元素 | 参数:Predicate(判断逻辑) 返回:Stream | list.stream().filter(n -> n % 2 == 0) // 保留偶数:2,4,6 |
| 映射 | map(Function<T, R> mapper) | 将元素通过Function转换为另一种类型(1:1转换) | 参数:Function(转换逻辑) 返回:Stream | list.stream().map(n -> n * 2) // 元素翻倍:2,4,6,8,10,12 |
| 扁平化映射 | flatMap(Function<T, Stream<R>> mapper) | 将元素转换为Stream后“扁平化”(如List -> List) | 参数:Function(返回Stream) 返回:Stream | List<List<Integer>> nested = Arrays.asList(Arrays.asList(1,2), Arrays.asList(3,4)); nested.stream().flatMap(List::stream) // 1,2,3,4 |
| 排序 | sorted() / sorted(Comparator<T> comparator) | 自然排序(需元素实现Comparable)/ 自定义排序(Comparator) | 无参:自然排序 有参:Comparator 返回:Stream | list.stream().sorted((a,b) -> b - a) // 降序:6,5,4,3,2,1 |
| 去重 | distinct() | 基于equals()去重(保留第一个出现的元素) | 返回:Stream | Stream.of(1,2,2,3).distinct() // 1,2,3 |
| 限制数量 | limit(long maxSize) | 保留前maxSize个元素(若流长度小于maxSize则保留全部) | 参数:最大长度 返回:Stream | list.stream().limit(3) // 1,2,3 |
| 跳过元素 | skip(long n) | 跳过前n个元素(若流长度小于n则返回空流) | 参数:跳过数量 返回:Stream | list.stream().skip(2) // 3,4,5,6 |
| peek(调试) | peek(Consumer<T> action) | 对每个元素执行Consumer操作(不改变元素,用于调试打印) | 参数:Consumer(消费逻辑) 返回:Stream | list.stream().peek(n -> System.out.println("处理:" + n)).filter(n -> n>3) // 打印所有元素,保留4,5,6 |
Stream终止操作(触发流处理,返回非Stream结果)
| 操作类型 | 方法示例 | 功能描述 | 返回值类型 | 示例代码(基于List<Integer> list = Arrays.asList(1,2,3,4,5,6);) |
|---|---|---|---|---|
| 收集结果 | collect(Collector<T, A, R> collector) | 将流元素收集为集合/映射等(配合Collectors工具类) | 任意类型R(如List、Set、Map) | list.stream().filter(n -> n>3).collect(Collectors.toList()) // [4,5,6] |
| 计数 | count() | 返回流中元素的数量 | long | list.stream().filter(n -> n%2==0).count() // 3(2,4,6) |
| 遍历 | forEach(Consumer<T> action) | 对每个元素执行Consumer操作(无返回值,终端操作) | void | list.stream().forEach(System.out::println) // 打印所有元素 |
| 匹配(任一) | anyMatch(Predicate<T> predicate) | 判断是否存在至少一个元素满足Predicate(短路操作:找到即返回) | boolean | list.stream().anyMatch(n -> n > 5) // true(6满足) |
| 匹配(所有) | allMatch(Predicate<T> predicate) | 判断是否所有元素都满足Predicate(短路操作:有一个不满足即返回) | boolean | list.stream().allMatch(n -> n > 0) // true |
| 匹配(无) | noneMatch(Predicate<T> predicate) | 判断是否所有元素都不满足Predicate(短路操作:有一个满足即返回) | boolean | list.stream().noneMatch(n -> n < 0) // true |
| 查找第一个 | findFirst() | 返回流中第一个元素(对于有序流可靠,并行流中也能返回第一个) | Optional | list.stream().filter(n -> n%2==0).findFirst() // Optional[2] |
| 查找任意 | findAny() | 返回流中任意一个元素(并行流下可能更快,结果不确定) | Optional | list.parallelStream().filter(n -> n>3).findAny() // 可能是4/5/6 |
| 最大值 | max(Comparator<T> comparator) | 根据Comparator返回流中最大元素 | Optional | list.stream().max(Integer::compare) // Optional[6] |
| 最小值 | min(Comparator<T> comparator) | 根据Comparator返回流中最小元素 | Optional | list.stream().min(Integer::compare) // Optional[1] |
| 归约(聚合) | reduce(T identity, BinaryOperator<T> accumulator) | 从初始值identity开始,通过BinaryOperator聚合元素(如求和、乘积) | T(非Optional,因有初始值) | list.stream().reduce(0, Integer::sum) // 21(1+2+3+4+5+6) |
| 归约(无初始值) | reduce(BinaryOperator<T> accumulator) | 无初始值的聚合(若流为空返回空Optional) | Optional | list.stream().reduce(Integer::sum) // Optional[21] |
注意:
Collectors 的核心价值是:提供了一套开箱即用的“收集策略”,让你无需手动编写复杂逻辑,就能将 Stream 处理后的元素高效转换为所需的结果形式(集合、统计值、分组数据等)。
它是 Stream 终止操作的“左膀右臂”——几乎所有 stream.collect(...) 都依赖 Collectors 提供的收集器来完成最终的结果生成。
Collectors 提供了大量静态方法,返回各种“收集器”(Collector 接口的实现类),覆盖绝大多数常见的收集场景:
1. 收集到集合(最基础)
将流元素收集到 List、Set、Map 等标准集合中。
toList():收集为List(默认是ArrayList)toSet():收集为Set(默认是HashSet)toMap():收集为Map(需指定键和值的生成规则)
2. 聚合统计(计算数值结果)
对元素进行计数、求和、求平均值等聚合操作。
counting():统计元素数量(返回Long)summingInt()/summingLong():对整数/长整型属性求和averagingInt():求整数属性的平均值(返回Double)maxBy()/minBy():根据比较器求最大/小元素
示例:
List<Student> students = Arrays.asList(new Student("张三", 18, 90),new Student("李四", 19, 85)
);// 统计学生数量
Long count = students.stream().collect(Collectors.counting()); // 2// 求分数总和
Integer totalScore = students.stream().collect(Collectors.summingInt(Student::getScore)); // 90+85=175// 求分数平均值
Double avgScore = students.stream().collect(Collectors.averagingInt(Student::getScore)); // 175/2=87.5
3. 分组与分区(按规则拆分元素)
groupingBy():按指定规则(如属性)将元素分组,返回Map<分组键, 元素列表>。partitioningBy():按布尔条件(Predicate)将元素分为两组,返回Map<Boolean, 元素列表>(只有两个键:true和false)。
示例:
// 按年龄分组(groupingBy)
Map<Integer, List<Student>> groupByAge = students.stream().collect(Collectors.groupingBy(Student::getAge));// 按“分数是否≥90”分区(partitioningBy)
Map<Boolean, List<Student>> partitionByScore = students.stream().collect(Collectors.partitioningBy(s -> s.getScore() >= 90));
// 结果:{true=[张三], false=[李四]}
4. 字符串拼接
joining():将流中的字符串元素按指定分隔符拼接(支持前缀、后缀)。
示例:
String joined = Stream.of("Java", "Stream", "Collectors").collect(Collectors.joining(", ")); // 结果:"Java, Stream, Collectors"// 带前缀和后缀
String withPrefixSuffix = Stream.of("a", "b").collect(Collectors.joining(", ", "{", "}")); // 结果:"{a, b}"
5. 转换元素后收集(mapping())
先对元素进行转换(如提取属性),再收集转换后的结果(常与 groupingBy 等组合使用)。
示例:
// 按年龄分组后,只保留每组学生的姓名(先转换再收集)
Map<Integer, List<String>> ageToNames = students.stream().collect(Collectors.groupingBy(Student::getAge, // 分组键:年龄Collectors.mapping(Student::getName, // 转换:提取姓名Collectors.toList() // 收集为列表))); // 结果:{18=[张三], 19=[李四]}
附:关于原始类型流
Stream 中的 mapToInt、flatMapToInt 等方法(还有类似的 mapToLong/flatMapToLong、mapToDouble/flatMapToDouble)属于“映射到原始类型流”的中间操作。它们的核心作用是:将元素从引用类型(如 Integer)转换为基本类型(如 int),并返回对应的原始类型流(如 IntStream),从而避免自动装箱/拆箱的性能损耗,同时提供针对基本类型的特有操作(如求和、求平均等)。
核心特点:
- 输入:流中的元素(通常是引用类型,如
Integer、String等)。 - 输出:原始类型流(
IntStream/LongStream/DoubleStream),而非普通的Stream<T>。 - 优势:处理基本类型时更高效(无装箱拆箱开销),且原始类型流提供了专属的终止操作(如
sum()、average())。
一、mapToInt:将元素转换为 int,返回 IntStream
方法签名:
IntStream mapToInt(ToIntFunction<? super T> mapper)
ToIntFunction是函数式接口,抽象方法为int applyAsInt(T value):接收一个元素T,返回一个int。
使用场景:当需要将流中的元素(如 Integer、String)转换为 int 类型,并进行数值计算时使用。
示例 1:将 Integer 流转为 IntStream 并求和
import java.util.Arrays;
import java.util.List;public class MapToIntDemo {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(1, 2, 3, 4);// 1. 用 mapToInt 将 Integer 转换为 int(避免装箱),得到 IntStream// 2. 用 IntStream 的 sum() 直接求和(普通 Stream 没有 sum() 方法)int sum = numbers.stream().mapToInt(Integer::intValue) // ToIntFunction:Integer -> int.sum(); // IntStream 特有方法:求和System.out.println("总和:" + sum); // 输出:10(1+2+3+4)}
}
示例 2:将字符串转为其长度的 int 流
List<String> words = Arrays.asList("apple", "banana", "cat");// 转换为字符串长度的 IntStream,求最大值
int maxLength = words.stream().mapToInt(String::length) // ToIntFunction:String -> 长度(int).max() // IntStream 特有:求最大值(返回 OptionalInt).orElse(0);System.out.println("最长字符串长度:" + maxLength); // 输出:6("banana"的长度)
二、flatMapToInt:将元素转换为 IntStream 并合并,返回单一 IntStream
方法签名:
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper)
mapper是函数式接口:接收一个元素T,返回一个IntStream(子流)。flatMapToInt会将所有子流合并为一个单一的IntStream(类似flatMap的“扁平化”逻辑,但针对int类型)。
使用场景:当流中的元素本身是“包含 int 的容器”(如 int[]、List<Integer>),需要将这些容器中的 int 全部提取出来,合并成一个 IntStream 时使用。
示例 1:处理 int 数组的列表,提取所有 int 并求和
import java.util.Arrays;
import java.util.List;public class FlatMapToIntDemo {public static void main(String[] args) {// 流中的元素是 int 数组List<int[]> arrays = Arrays.asList(new int[]{1, 2},new int[]{3, 4, 5},new int[]{6});// 1. 用 flatMapToInt 将每个 int[] 转为 IntStream(Arrays::stream 可将数组转为流)// 2. 合并所有子流为一个 IntStream,再求和int total = arrays.stream().flatMapToInt(Arrays::stream) // mapper:int[] -> IntStream.sum();System.out.println("总和:" + total); // 输出:21(1+2+3+4+5+6)}
}
示例 2:将字符串列表拆分为字符的 ASCII 码流
List<String> words = Arrays.asList("ab", "cd");// 每个字符串拆分为字符,转换为 ASCII 码(int),合并为 IntStream
IntStream asciiStream = words.stream().flatMapToInt(s -> {// 将字符串的每个字符转为 ASCII 码(int),生成 IntStreamreturn s.chars(); // String 的 chars() 方法直接返回 IntStream});// 打印所有 ASCII 码(a=97, b=98, c=99, d=100)
asciiStream.forEach(System.out::println); // 输出:97 98 99 100
三、原始类型流(IntStream 等)的特有操作
IntStream/LongStream/DoubleStream 提供了普通 Stream 没有的终止操作,专为数值计算设计:
| 方法 | 功能描述 | 示例(基于 IntStream) |
|---|---|---|
sum() | 求所有元素的和 | int sum = intStream.sum(); |
average() | 求平均值(返回 OptionalDouble) | double avg = intStream.average().orElse(0); |
max()/min() | 求最大/小值(返回 OptionalInt) | int max = intStream.max().orElse(-1); |
count() | 统计元素数量(返回 long) | long count = intStream.count(); |
toArray() | 转换为 int[] 数组 | int[] arr = intStream.toArray(); |
Stream编程实践题
以下是13道融合Stream流与函数式编程的Java编程题,涵盖过滤、转换、聚合、分组、排序等核心场景,每道题均附详细解题思路和代码实现:
题目1:过滤偶数并求平方和
需求:给定整数列表,过滤出所有偶数,计算这些偶数的平方和。
知识点:filter()、map()、mapToInt()、sum()
答案:
import java.util.Arrays;
import java.util.List;public class EvenSquareSum {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);// 过滤偶数 -> 转平方 -> 求和int sum = numbers.stream().filter(n -> n % 2 == 0) // 函数式接口Predicate:判断是否为偶数.map(n -> n * n) // 函数式接口Function:转换为平方.mapToInt(Integer::intValue) // 转换为IntStream便于求和.sum();System.out.println("偶数平方和:" + sum); // 输出:4+16+36+64=120}
}
题目2:字符串处理与排序
需求:给定字符串列表,过滤出长度大于3的字符串,转换为大写后按自然顺序排序,最终收集为列表。
知识点:filter()、map()、sorted()、collect(Collectors.toList())
答案:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class StringProcess {public static void main(String[] args) {List<String> words = Arrays.asList("apple", "cat", "banana", "dog", "grape");List<String> result = words.stream().filter(s -> s.length() > 3) // 过滤长字符串.map(String::toUpperCase) // 方法引用:转大写.sorted() // 自然排序(Comparable).collect(Collectors.toList()); // 收集结果System.out.println(result); // 输出:[APPLE, BANANA, GRAPE]}
}
题目3:对象分组统计
需求:定义Student类(含name和age),给定学生列表,按年龄分组并统计每个年龄的学生数量。
知识点:Collectors.groupingBy()、方法引用、Collectors.counting()
答案:
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}public int getAge() { return age; }
}public class StudentGroup {public static void main(String[] args) {List<Student> students = Arrays.asList(new Student("Alice", 18),new Student("Bob", 19),new Student("Charlie", 18),new Student("David", 19));// 按年龄分组,值为该年龄的人数Map<Integer, Long> ageCount = students.stream().collect(Collectors.groupingBy(Student::getAge, // 分类函数(方法引用)Collectors.counting() // 统计函数));System.out.println(ageCount); // 输出:{18=2, 19=2}}
}
题目4:求列表最大值
需求:给定整数列表,使用Stream求最大值(若列表为空则返回-1)。
知识点:max()、Comparator、orElse()
答案:
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;public class FindMax {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(3, 7, 2, 9, 5);// List<Integer> numbers = Collections.emptyList(); // 测试空列表int max = numbers.stream().max(Comparator.naturalOrder()) // 自然排序比较器.orElse(-1); // 空列表时返回默认值System.out.println("最大值:" + max); // 输出:9(非空列表);-1(空列表)}
}
或者
import java.util.Arrays;
import java.util.List;
import java.util.Comparator;
import java.util.stream.Collectors;public class FindMax {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(3, 7, 2, 9, 5);// List<Integer> numbers = Arrays.asList(); // 测试空列表// 1. 用Collectors.maxBy获取最大值(返回Optional<Integer>)// 2. 用orElse(-1)处理空列表情况int max = numbers.stream().collect(Collectors.maxBy(Comparator.comparingInt(Integer::intValue) // 补全比较器:比较Integer的int值)).orElse(-1); // 空列表时返回-1System.out.println("最大值:" + max); // 输出:9(非空列表);-1(空列表)}
}
- Comparator.comparingInt(Integer::intValue):Collectors.maxBy 需要一个 Comparator 来定义比较规则。此处用 comparingInt 接收 ToIntFunction(通过 Integer::intValue 提取整数的原始值),实现对 Integer 元素的比较。
- orElse(-1):Collectors.maxBy 返回 Optional(可能为空,如列表为空时)。orElse(-1) 表示:若 Optional 有值则取其值,否则返回默认值 -1,完美处理空列表场景。
题目5:嵌套列表去重合并
需求:给定列表的列表(List<List<Integer>>),提取所有元素并去重,最终收集为Set。
知识点:flatMap()、distinct()、collect(Collectors.toSet())
//flatMap 的参数本身就是一个函数式接口,它的作用就是定义 “如何将流中的每个元素转换为子流”。
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
flatMap 是 Stream 中用于扁平化流的中间操作,核心作用是:将流中每个元素转换为一个子流,然后把所有子流“合并”成一个单一的流(消除嵌套结构)。
一句话理解: 如果流中的元素本身是“容器”(如 List、Set 或其他流),flatMap 能把这些容器“拆开”,让容器里的元素直接成为新流的元素,实现从“嵌套流”到“单层流”的转换。
与 map 的对比(关键区别):
map:对每个元素做1:1转换(如把String转成Integer),结果是“元素类型转换后的流”(如Stream<String> → Stream<Integer>)。flatMap:对每个元素做1:N转换(如把List<Integer>转成Stream<Integer>),然后把所有子流合并,结果是“消除嵌套的单层流”(如Stream<List<Integer>> → Stream<Integer>)。
答案:
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;public class FlatMapDemo {public static void main(String[] args) {List<List<Integer>> nestedList = Arrays.asList(Arrays.asList(1, 2, 3),Arrays.asList(2, 3, 4),Arrays.asList(3, 4, 5));// 扁平化嵌套列表 -> 去重 -> 收集为SetSet<Integer> uniqueElements = nestedList.stream().flatMap(List::stream) // 展平为Stream<Integer>.distinct() // 去重.collect(Collectors.toSet());System.out.println(uniqueElements); // 输出:[1, 2, 3, 4, 5]}
}
题目6:字符串拼接
需求:给定字符串列表,用逗号拼接所有元素(如["a", "b", "c"] -> "a,b,c")。
知识点:Collectors.joining()
答案:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class StringJoin {public static void main(String[] args) {List<String> parts = Arrays.asList("Java", "Stream", "Practice");String joined = parts.stream().collect(Collectors.joining(",")); // 按逗号拼接System.out.println(joined); // 输出:Java,Stream,Practice}
}
题目7:统计可被3整除的数字
需求:给定整数列表,统计能被3整除的数字的个数。
知识点:filter()、count()
答案:
import java.util.Arrays;
import java.util.List;public class CountDivisibleBy3 {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(3, 6, 9, 10, 12, 14);long count = numbers.stream().filter(n -> n % 3 == 0) // 过滤可被3整除的数.count(); // 统计数量System.out.println("可被3整除的数有:" + count + "个"); // 输出:4个}
}
题目8:筛选成绩前3名学生
需求:定义Student类(含name和score),给定学生列表,筛选出成绩>80的学生,按成绩降序排序后取前3名的姓名。
知识点:filter()、sorted(Comparator)、limit()、map()
答案:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;class Student2 {private String name;private int score;public Student2(String name, int score) {this.name = name;this.score = score;}public String getName() { return name; }public int getScore() { return score; }
}public class Top3Students {public static void main(String[] args) {List<Student2> students = Arrays.asList(new Student2("Alice", 95),new Student2("Bob", 78),new Student2("Charlie", 88),new Student2("David", 92),new Student2("Eve", 85));List<String> top3Names = students.stream().filter(s -> s.getScore() > 80) // 筛选及格学生.sorted(Comparator.comparingInt(Student2::getScore).reversed()) // 成绩降序.limit(3) // 取前3名.map(Student2::getName) // 提取姓名.collect(Collectors.toList());System.out.println("成绩前3名:" + top3Names); // 输出:[Alice, David, Charlie]}
}
题目9:计算列表元素乘积
需求:给定整数列表,使用reduce()计算所有元素的乘积(空列表返回0)。
知识点:reduce()、orElse()
答案:
import java.util.Arrays;
import java.util.Collections;
import java.util.List;public class ProductReduce {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(2, 3, 4, 5);// List<Integer> numbers = Collections.emptyList(); // 测试空列表int product = numbers.stream().reduce(1, (a, b) -> a * b); // 初始值1,累积器:相乘// 等价于 .reduce(1, Integer::multiplyExact)// 处理空列表(reduce无初始值时返回Optional)int safeProduct = numbers.stream().reduce((a, b) -> a * b).orElse(0);System.out.println("乘积:" + product); // 输出:120(非空)System.out.println("安全乘积:" + safeProduct); // 输出:120(非空);0(空)}
}
题目10:数字分区(偶数/奇数)
需求:给定整数列表,将其分为“偶数”和“奇数”两个列表,用Map<Boolean, List<Integer>>存储(true对应偶数,false对应奇数)。
知识点:Collectors.partitioningBy()
答案:
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;public class NumberPartition {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);// 按是否为偶数分区Map<Boolean, List<Integer>> partition = numbers.stream().collect(Collectors.partitioningBy(n -> n % 2 == 0 // 分区条件));System.out.println("偶数:" + partition.get(true)); // 输出:[2,4,6,8]System.out.println("奇数:" + partition.get(false)); // 输出:[1,3,5,7]}
}
题目11:对象属性拼接
需求:定义Person类(含name和age),将List<Person>转换为List<String>,每个元素格式为“姓名:年龄”。
知识点:map()、lambda表达式字符串拼接
答案:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() { return name; }public int getAge() { return age; }
}public class PersonToString {public static void main(String[] args) {List<Person> people = Arrays.asList(new Person("张三", 20),new Person("李四", 25),new Person("王五", 30));List<String> personInfo = people.stream().map(p -> p.getName() + ":" + p.getAge()) // 拼接属性.collect(Collectors.toList());System.out.println(personInfo); // 输出:[张三:20, 李四:25, 王五:30]}
}
题目12:使用Function接口转换
需求:定义一个Function<String, Integer>接口实例,用于计算字符串长度,并用它转换字符串列表为长度列表。
知识点:Function函数式接口、map()
答案:
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;public class FunctionDemo {public static void main(String[] args) {List<String> words = Arrays.asList("apple", "banana", "cherry");// 定义Function:字符串 -> 长度Function<String, Integer> stringToLength = String::length; // 方法引用// 应用Function转换List<Integer> lengths = words.stream().map(stringToLength) // 用Function转换每个元素.collect(Collectors.toList());System.out.println("字符串长度:" + lengths); // 输出:[5, 6, 6]}
}
题目13:计算正整数平均值
需求:给定整数列表,过滤掉负数后计算平均值(无正整数时返回0.0)。
知识点:filter()、mapToInt()、average()、orElse()
答案:
import java.util.Arrays;
import java.util.List;public class PositiveAverage {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(-3, 5, -2, 8, 10);// List<Integer> numbers = Arrays.asList(-1, -2, -3); // 测试无正整数double average = numbers.stream().filter(n -> n > 0) // 过滤正整数.mapToInt(Integer::intValue) // 转IntStream.average() // 计算平均值(返回OptionalDouble).orElse(0.0); // 无元素时返回0.0System.out.println("正整数平均值:" + average); // 输出:(5+8+10)/3=7.666...}
}
