【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.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class LambdaDemo {
public static void main(String[] args) {
// 1. 基本的字符串列表操作
ArrayList<String> fruits = new ArrayList<>();
Collections.addAll(fruits, "apple", "banana", "cherry", "date", "elderberry");
System.out.println("=== Lambda表达式示例 ===");
// 传统匿名内部类方式
System.out.println("\n1. 传统匿名内部类排序:");
Comparator<String> lengthComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
};
ArrayList<String> copy1 = new ArrayList<>(fruits);
copy1.sort(lengthComparator);
System.out.println("按长度排序: " + copy1);
// Lambda简化版
System.out.println("\n2. Lambda表达式排序:");
Comparator<String> lambdaComparator = (s1, s2) -> Integer.compare(s1.length(), s2.length());
ArrayList<String> copy2 = new ArrayList<>(fruits);
copy2.sort(lambdaComparator);
System.out.println("按长度排序: " + copy2);
// 最简洁的使用方式
System.out.println("\n3. 直接使用Lambda:");
ArrayList<String> copy3 = new ArrayList<>(fruits);
copy3.sort((s1, s2) -> Integer.compare(s1.length(), s2.length()));
System.out.println("按长度排序: " + copy3);
// 2. 其他常见Lambda表达式示例
System.out.println("\n=== 更多Lambda示例 ===");
// Predicate示例 - 过滤
System.out.println("\n4. 过滤长度大于5的字符串:");
List<String> longNames = fruits.stream()
.filter(s -> s.length() > 5)
.toList();
System.out.println("结果: " + longNames);
// Function示例 - 转换
System.out.println("\n5. 将字符串转换为大写:");
List<String> upperCase = fruits.stream()
.map(String::toUpperCase)
.toList();
System.out.println("结果: " + upperCase);
// Consumer示例 - 遍历处理
System.out.println("\n6. 遍历并打印每个元素:");
fruits.forEach(item -> System.out.println("水果: " + item));
// 3. 方法引用示例
System.out.println("\n=== 方法引用示例 ===");
// 静态方法引用
System.out.println("\n7. 使用方法引用排序:");
ArrayList<String> copy4 = new ArrayList<>(fruits);
copy4.sort(Comparator.comparing(String::length));
System.out.println("按长度排序: " + copy4);
// 实例方法引用
System.out.println("\n8. 按字典序排序:");
ArrayList<String> copy5 = new ArrayList<>(fruits);
copy5.sort(String::compareTo);
System.out.println("字典序: " + copy5);
// 构造方法引用
System.out.println("\n9. 构造方法引用示例:");
List<String> stringList = Arrays.asList("hello", "world", "java");
List<StringBuilder> builders = stringList.stream()
.map(StringBuilder::new)
.collect(Collectors.toList());
System.out.println("StringBuilder列表: " + builders);
System.out.println("\n=== 示例结束 ===");
}
}
案例2:使用Lambda实现Predicate过滤
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PredicateDemo {
public static void main(String[] args) {
ArrayList<String> fruits = new ArrayList<>();
Collections.addAll(fruits, "apple", "banana", "cherry", "date", "elderberry", "fig");
System.out.println("=== Predicate接口示例 ===");
System.out.println("原始列表: " + fruits);
// 1. 传统匿名内部类方式
System.out.println("\n1. 传统匿名内部类演示:");
Predicate<String> containsAPredicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("a");
}
};
System.out.println("containsAPredicate 测试 'apple': " + containsAPredicate.test("apple")); // true
System.out.println("containsAPredicate 测试 'cherry': " + containsAPredicate.test("cherry")); // false
Predicate<String> negatedPredicate = containsAPredicate.negate();
System.out.println("negatedPredicate 测试 'apple': " + negatedPredicate.test("apple")); // false
System.out.println("negatedPredicate 测试 'cherry': " + negatedPredicate.test("cherry")); // true
ArrayList<String> traditionalFilter = new ArrayList<>(fruits);
traditionalFilter.removeIf(negatedPredicate); // 移除不包含'a'的
System.out.println("保留包含'a'的结果: " + traditionalFilter);
// 2. Lambda表达式简化
System.out.println("\n2. Lambda表达式过滤包含'a'的单词:");
ArrayList<String> lambdaFilter = new ArrayList<>(fruits);
lambdaFilter.removeIf(s -> !s.contains("a")); // 保留包含'a'的
System.out.println("结果: " + lambdaFilter);
// 3. Stream API结合Predicate
System.out.println("\n3. Stream API过滤:");
List<String> streamResult = fruits.stream()
.filter(s -> s.contains("a"))
.toList();
System.out.println("包含'a'的单词: " + streamResult);
// 4. 复合Predicate示例
System.out.println("\n4. 复合条件过滤:");
Predicate<String> lengthPredicate = s -> s.length() > 4;
Predicate<String> containsEPredicate = s -> s.contains("e");
// 长度大于4且包含'e'的单词
List<String> compoundResult = fruits.stream()
.filter(lengthPredicate.and(containsEPredicate))
.toList();
System.out.println("长度>4且包含'e': " + compoundResult);
// 5. 或条件示例
System.out.println("\n5. 或条件过滤:");
List<String> orResult = fruits.stream()
.filter(lengthPredicate.or(containsEPredicate))
.toList();
System.out.println("长度>4或包含'e': " + orResult);
// 6. 取反示例
System.out.println("\n6. 取反条件过滤:");
List<String> negateResult = fruits.stream()
.filter(lengthPredicate.negate())
.toList();
System.out.println("长度<=4的单词: " + negateResult);
// 7. 自定义复杂Predicate
System.out.println("\n7. 自定义复杂条件:");
Predicate<String> customPredicate = s -> {
// 长度为偶数且不以元音开头
boolean evenLength = s.length() % 2 == 0;
boolean notStartWithVowel = !Arrays.asList('a', 'e', 'i', 'o', 'u').contains(Character.toLowerCase(s.charAt(0)));
return evenLength && notStartWithVowel;
};
List<String> customResult = fruits.stream()
.filter(customPredicate)
.toList();
System.out.println("自定义条件结果: " + customResult);
System.out.println("\n=== Predicate示例结束 ===");
}
}
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代码。
建议在实际开发中多使用这些新特性,逐步培养函数式编程思维,这将为你应对复杂数据处理场景提供强大的工具支持。
更多推荐
所有评论(0)