【Java零基础·第12章】Lambda与Stream API
在Java编程的学习路上,集合框架是基础中的基础,而Java 8引入的Lambda表达式和Stream API则为我们带来了编程范式的革新。本文将从集合迭代器的复习入手,逐步过渡到Lambda表达式的优雅语法,再深入探讨Stream API的强大功能,帮助你理解这一技术演进的脉络与实践技巧。
一、集合迭代器:遍历集合的基石
在Java中,遍历集合是最常见的操作之一,而迭代器(Iterator)正是实现这一操作的核心工具。
1.1 迭代器的核心概念
-
Iterator接口:所有集合遍历的基础接口,包含两个核心方法:
boolean hasNext()
:判断是否还有下一个元素E next()
:获取下一个元素
-
列表迭代器ListIterator:专为List集合设计的增强版迭代器,相比普通Iterator新增了更多功能:
特性 | 普通迭代器Iterator | 列表迭代器ListIterator |
---|---|---|
遍历范围 | 所有Collection集合 | 仅限List集合 |
反向遍历 | 不支持 | 支持(hasPrevious()/previous()) |
元素修改 | 仅支持删除 | 支持添加(add())、修改(set()) |
下标获取 | 不支持 | 支持(nextIndex()/previousIndex()) |
1.2 Iterable与foreach的关系
Iterable
接口是所有可迭代集合的父接口,它包含一个iterator()
方法用于获取迭代器。而我们常用的foreach循环(增强for循环)本质上就是Iterable
接口的语法糖,编译器会自动将其转换为迭代器的操作。
注意:在使用foreach或Iterator遍历集合时,绝对不能直接调用集合的add/remove/sort等方法,否则会抛出ConcurrentModificationException
异常。这是因为迭代器内部维护了修改计数器,集合自身的修改会导致计数器不匹配。
二、集合与数组:数据存储的两面
在深入新特性之前,我们先明确集合与数组的核心区别:
特性 | 数组 | 集合 |
---|---|---|
元素类型 | 支持基本类型和对象 | 仅支持对象(自动装箱拆箱是语法糖) |
扩容机制 | 固定大小,不支持自动扩容 | 动态扩容,无需手动管理大小 |
数据结构 | 仅线性结构 | 丰富多样(列表、集合、映射、队列等) |
三、Lambda表达式:函数式编程的Java实现
Java 8引入的Lambda表达式是一种语法糖,主要用于简化函数式接口的匿名内部类实现,让代码更简洁、更具表达力。
3.1 函数式接口:Lambda的基石
函数式接口(SAM接口 - Single Abstract Method)是指只包含一个抽象方法的接口(可以包含多个静态方法和默认方法)。Java 8中推荐使用@FunctionalInterface
注解标记这类接口。
常见的函数式接口:
Comparator<T>
:int compare(T t1, T t2)
Predicate<T>
:boolean test(T t)
Consumer<T>
:void accept(T t)
Function<T, R>
:R apply(T t)
3.2 Lambda表达式的语法
基本语法:
(参数列表) -> { 方法体 }
简化规则:
- 参数类型可省略(编译器可自动推断)
- 单参数时可省略括号
()
- 方法体只有一条语句时,可省略
{}
和;
- 若唯一语句是return,需同时省略return
案例1:使用Lambda实现Comparator排序
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;public class LambdaDemo {public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();Collections.addAll(list, "apple", "banana", "cherry", "date");// 传统匿名内部类方式Comparator<String> lengthComparator = new Comparator<String>() {@Overridepublic int compare(String s1, String s2) {return Integer.compare(s1.length(), s2.length());}};// Lambda简化版Comparator<String> lambdaComparator = (s1, s2) -> Integer.compare(s1.length(), s2.length());// 更简洁的使用方式list.sort((s1, s2) -> Integer.compare(s1.length(), s2.length()));System.out.println(list); // [date, apple, banana, cherry]}
}
案例2:使用Lambda实现Predicate过滤
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;public class PredicateDemo {public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();Collections.addAll(list, "apple", "banana", "cherry", "date");// 过滤包含字母'a'的单词// 传统匿名内部类Predicate<String> containsAPredicate = new Predicate<String>() {@Overridepublic boolean test(String s) {return s.contains("a");}};// Lambda简化list.removeIf(s -> s.contains("a"));System.out.println(list); // [cherry]}
}
3.3 四大函数式接口家族
Java 8在java.util.function
包中提供了丰富的函数式接口,可分为四大类:
-
判断型(Predicate):返回boolean,用于条件判断
// 检查字符串长度是否大于5 Predicate<String> lengthCheck = s -> s.length() > 5; System.out.println(lengthCheck.test("hello")); // false
-
消费型(Consumer):无返回值,用于处理数据
// 打印字符串及其长度 Consumer<String> printInfo = s -> System.out.println(s + " length: " + s.length()); printInfo.accept("hello"); // hello length: 5
-
功能型(Function):有输入输出,用于数据转换
// 将字符串转换为其长度 Function<String, Integer> stringToLength = s -> s.length(); System.out.println(stringToLength.apply("hello")); // 5
-
供给型(Supplier):无输入有输出,用于产生数据
// 生成随机整数 Supplier<Integer> randomInt = () -> (int)(Math.random() * 100); System.out.println(randomInt.get()); // 随机数
四、方法引用:Lambda的进一步简化
方法引用是Lambda表达式的简化形式,当Lambda体只是调用一个已存在的方法时,可以使用方法引用进一步简化代码。
4.1 方法引用的三种形式
-
对象::实例方法
// 打印输出 Consumer<String> printer = System.out::println; printer.accept("Hello World");
-
类::静态方法
// 整数比较 Comparator<Integer> comparator = Integer::compare; System.out.println(comparator.compare(3, 5)); // -1
-
类::实例方法(第一个参数是方法的调用者)
// 字符串比较 Comparator<String> strComparator = String::compareTo; System.out.println(strComparator.compare("a", "b")); // -1
-
类::new(构造器引用)
// 创建字符串数组 Supplier<String> stringCreator = String::new; String s = stringCreator.get(); // 空字符串
4.2 方法引用使用条件
使用方法引用需要满足三个条件:
- Lambda体中只有一个语句
- 该语句是调用某个方法(静态或实例方法)
- 方法的参数与Lambda表达式的参数完全一致
案例:方法引用实战
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;public class MethodReferenceDemo {public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();Collections.addAll(list, "apple", "banana", "cherry", "date");// 1. 对象::实例方法list.forEach(System.out::println);// 2. 类::静态方法list.sort(Integer::compare); // 编译错误!参数类型不匹配// 正确示例:按长度排序list.sort(Comparator.comparingInt(String::length));System.out.println(list); // [date, apple, banana, cherry]// 3. 类::实例方法list.sort(String::compareTo);System.out.println(list); // [apple, banana, cherry, date]}
}
五、Stream API:数据处理的流水线
Stream API是Java 8引入的另一个重大特性,它为集合数据的处理提供了类似SQL查询的声明式操作,让数据处理更简洁、高效。
5.1 Stream的核心特点
-
三步操作流程:
- 创建Stream:从数据源获取流
- 中间操作:对数据进行处理(过滤、映射、排序等)
- 终结操作:产生结果(收集、统计、遍历等)
-
懒加载特性:中间操作不会立即执行,直到遇到终结操作才会一次性处理
-
不可变性:Stream操作不会修改源数据,每个操作都会返回新的Stream
-
一次性:Stream只能被消费一次,再次使用会抛出异常
5.2 创建Stream的常用方式
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;public class StreamCreation {public static void main(String[] args) {// 1. 从集合创建List<String> list = new ArrayList<>();Stream<String> streamFromList = list.stream();// 2. 从数组创建int[] array = {1, 2, 3, 4, 5};IntStream streamFromArray = Arrays.stream(array);// 3. 直接创建Stream<String> streamFromValues = Stream.of("a", "b", "c");// 4. 生成无限流(需要限制大小)Stream<Integer> infiniteStream = Stream.generate(() -> (int)(Math.random() * 100)).limit(5); // 只取前5个元素// 5. 迭代生成Stream<Integer> iteratedStream = Stream.iterate(0, n -> n + 2).limit(5); // 0, 2, 4, 6, 8}
}
5.3 中间操作:数据处理的流水线
常用中间操作:
方法 | 描述 |
---|---|
filter(Predicate) | 过滤元素 |
map(Function) | 元素转换 |
sorted() /sorted(Comparator) | 排序 |
distinct() | 去重(基于equals()) |
limit(long) | 限制元素数量 |
skip(long) | 跳过前n个元素 |
peek(Consumer) | 调试用,查看流中元素 |
案例:中间操作链
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;public class StreamMiddleOperations {public static void main(String[] args) {List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "apple");// 处理流程:去重 -> 过滤长度>5 -> 转换为大写 -> 排序Stream<String> processedStream = words.stream().distinct().filter(word -> word.length() > 5).map(String::toUpperCase).sorted();// 终结操作:打印结果processedStream.forEach(System.out::println);// 输出:APPLE, BANANA, CHERRY}
}
5.4 终结操作:获取处理结果
常用终结操作:
方法 | 描述 |
---|---|
forEach(Consumer) | 遍历元素 |
collect(Collector) | 收集结果到集合 |
count() | 计数 |
findFirst() /findAny() | 查找元素 |
allMatch() /anyMatch() /noneMatch() | 匹配判断 |
max(Comparator) /min(Comparator) | 最大值/最小值 |
reduce() | 归约计算 |
案例:综合数据处理
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;public class StreamTerminalOperations {public static void main(String[] args) {List<Student> students = Arrays.asList(new Student("Alice", 90),new Student("Bob", 85),new Student("Charlie", 95),new Student("David", 80),new Student("Eve", 90));// 1. 收集成绩大于85的学生姓名List<String> highScorers = students.stream().filter(s -> s.getScore() > 85).map(Student::getName).collect(Collectors.toList());System.out.println("高分学生: " + highScorers);// 2. 计算平均成绩double averageScore = students.stream().mapToInt(Student::getScore).average().orElse(0);System.out.println("平均成绩: " + averageScore);// 3. 查找最高分学生Optional<Student> topStudent = students.stream().max((s1, s2) -> Integer.compare(s1.getScore(), s2.getScore()));topStudent.ifPresent(s -> System.out.println("最高分学生: " + s.getName()));}static class Student {private String name;private int score;public Student(String name, int score) {this.name = name;this.score = score;}public String getName() { return name; }public int getScore() { return score; }}
}
六、总结:从命令式到声明式的转变
Java的发展历程中,从早期的集合迭代到Lambda表达式,再到Stream API,体现了从命令式编程到声明式编程的转变:
- 命令式编程:关注"怎么做",需要手动控制每一步操作(如使用for循环遍历)
- 声明式编程:关注"做什么",只需描述想要的结果(如Stream API的链式操作)
Lambda表达式和Stream API不仅让代码更简洁,还提高了可读性和可维护性,同时为并行处理提供了天然支持。掌握这些特性,能让你编写更优雅、高效的Java代码。
建议在实际开发中多使用这些新特性,逐步培养函数式编程思维,这将为你应对复杂数据处理场景提供强大的工具支持。