一、Lambda 表达式
1. Lambda 表达式简介
Lambda 表达式本质上是函数式接口的匿名实现,它用简洁的语法替代了传统的匿名内部类写法,核心格式如下:
(参数列表) -> { 方法体 }
举个最经典的对比:
// 传统匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello, Java 8!");
}
};
// Lambda 表达式写法
Runnable lambdaRunnable = () -> System.out.println("Hello, Java 8!");
它的优势在于:
- 代码行数大幅减少,去掉了冗余的接口声明、方法重写模板
- 更贴近 "函数式" 的表达,逻辑直接聚焦在 "要做什么",而非 "怎么写模板"
2. Lambda 表达式实现函数式接口
Lambda 表达式只能用于函数式接口 (只有一个抽象方法的接口),比如 Runnable、Callable、Comparator,以及 java.util.function 包下的内置函数式接口。
我们以 Comparator 为例,给一个列表排序:
List<String> words = Arrays.asList("banana", "apple", "cherry");
// 传统写法
Collections.sort(words, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
// Lambda 写法
Collections.sort(words, (a, b) -> a.length() - b.length());
这里的 Comparator 就是一个函数式接口,Lambda 表达式直接实现了它唯一的 compare 方法。
3. Lambda 表达式调用外部变量
Lambda 表达式可以访问外部的局部变量,但这些变量默认是隐式 final 的(即使你不写 final,也不能修改),这是为了保证多线程环境下的线程安全。
final int limit = 5; // 即使去掉 final 也不能修改
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream()
.filter(n -> n > limit) // 访问外部变量 limit
.forEach(System.out::println);
// 错误示例:在 Lambda 中修改外部变量会编译报错
// int count = 0;
// numbers.forEach(n -> count++); // 编译失败!
如果你需要在 Lambda 中修改值,可以使用 AtomicInteger 等包装类:
AtomicInteger count = new AtomicInteger(0);
numbers.forEach(n -> count.incrementAndGet());
System.out.println(count.get()); // 输出 6
4. Lambda 表达式与异常处理
Lambda 表达式本身不能直接抛出受检异常,如果你需要处理异常,有两种方式:
-
在 Lambda 内部用
try-catch包裹 -
自定义函数式接口,声明
throws异常// 方式1:内部 try-catch
List<String> files = Arrays.asList("a.txt", "b.txt");
files.forEach(file -> {
try {
Files.readAllLines(Paths.get(file));
} catch (IOException e) {
e.printStackTrace();
}
});// 方式2:自定义带异常的函数式接口
@FunctionalInterface
interface FileProcessor {
void process(String file) throws IOException;
}// 使用自定义接口
FileProcessor processor = file -> Files.readAllLines(Paths.get(file));
二、方法引用:Lambda 的 "进阶缩写"
方法引用是 Lambda 表达式的进一步简化,当 Lambda 只是调用一个已存在的方法时,就可以用方法引用替代,让代码更简洁。
1. 引用静态方法
语法:类名::静态方法名
// Lambda 写法
Function<String, Integer> stringToInt = s -> Integer.parseInt(s);
// 方法引用写法
Function<String, Integer> stringToIntRef = Integer::parseInt;
2. 引用成员方法
语法:对象::实例方法名 或 类名::实例方法名
List<String> list = Arrays.asList("a", "b", "c");
// 方式1:对象::实例方法
Consumer<String> printer = System.out::println;
list.forEach(printer);
// 方式2:类名::实例方法(适用于参数为调用者的情况)
Comparator<String> comparator = String::compareTo;
3. 引用带泛型的方法
方法引用也支持泛型,比如 List::add:
Stream.of(1, 2, 3)
.forEach(System.out::println); // 引用带泛型的方法
4. 引用构造方法
语法:类名::new,常用于创建对象:
// Lambda 写法
Supplier<List<String>> listSupplier = () -> new ArrayList<>();
// 方法引用写法
Supplier<List<String>> listSupplierRef = ArrayList::new;
// 带参数的构造方法引用
Function<Integer, List<Integer>> listCreator = ArrayList::new;
List<Integer> nums = listCreator.apply(10); // 创建初始容量为10的 ArrayList
5. Function 接口:内置函数式接口
java.util.function 包提供了大量内置的函数式接口,最常用的有:
| 接口 | 抽象方法 | 用途 |
|---|---|---|
Function<T,R> |
R apply(T t) |
接收一个参数,返回一个结果 |
Consumer<T> |
void accept(T t) |
接收一个参数,无返回值 |
Supplier<T> |
T get() |
不接收参数,返回一个结果 |
Predicate<T> |
boolean test(T t) |
接收一个参数,返回布尔值 |
它们的用法都和前面的例子类似,比如 Predicate 常用于过滤:
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
三、Stream 流处理:集合操作的 "流水线"
Stream 是 Java 8 引入的流处理 API,它可以让你以声明式的方式处理集合数据,就像工厂流水线一样,对数据进行过滤、映射、排序、收集等操作,代码更简洁,也更容易实现并行处理。
1. Stream 接口简介
Stream 的操作分为三类:
-
创建流:从集合、数组、文件等数据源创建流
-
中间操作:对流进行处理(过滤、映射等),返回新的流(可以链式调用)
-
终端操作 :触发计算,关闭流(比如
forEach、collect)// 从集合创建流
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();// 并行流
Stream<String> parallelStream = list.parallelStream();
2. Optional 类:告别空指针异常
Optional 是一个容器类,用来解决空指针异常问题,它可以包裹一个可能为 null 的值,提供安全的处理方式。
// 创建 Optional 对象
Optional<String> opt = Optional.ofNullable("hello");
Optional<String> emptyOpt = Optional.empty();
// 安全获取值
String value = opt.orElse("default"); // 如果 opt 为 null,返回默认值
String value2 = opt.orElseGet(() -> "default"); // 延迟加载默认值
opt.ifPresent(System.out::println); // 如果值存在,执行操作
3. Collectors 类:流结果的收集器
Collectors 类提供了大量静态方法,用来将 Stream 的结果收集到集合、字符串、统计结果中:
List<String> list = Arrays.asList("apple", "banana", "cherry");
// 收集到 List
List<String> resultList = list.stream().collect(Collectors.toList());
// 收集到 Set
Set<String> resultSet = list.stream().collect(Collectors.toSet());
// 拼接字符串
String joined = list.stream().collect(Collectors.joining(","));
// 分组
Map<Integer, List<String>> groupByLength = list.stream()
.collect(Collectors.groupingBy(String::length));
4. 数据过滤:filter
filter 用于根据条件过滤数据,保留符合条件的元素:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0) // 过滤偶数
.collect(Collectors.toList());
// 结果:[2, 4, 6]
5. 数据映射:map/flatMap
map 用于将流中的元素映射成另一种类型,flatMap 用于处理嵌套集合,将多个流合并成一个流:
// map 示例:将字符串转成大写
List<String> words = Arrays.asList("apple", "banana");
List<String> upperWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// flatMap 示例:处理嵌套列表
List<List<Integer>> nestedList = Arrays.asList(Arrays.asList(1,2), Arrays.asList(3,4));
List<Integer> flatList = nestedList.stream()
.flatMap(List::stream) // 将子列表的流合并成一个流
.collect(Collectors.toList());
// 结果:[1,2,3,4]
6. 数据查找:find/first/match
Stream 提供了多种查找和匹配的方法:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 查找第一个元素
Optional<Integer> first = numbers.stream().findFirst();
// 查找任意元素(并行流中更高效)
Optional<Integer> any = numbers.parallelStream().findAny();
// 判断是否所有元素都满足条件
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // false
// 判断是否存在元素满足条件
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true
7. 数据收集:collect
除了前面提到的 Collectors,collect 还支持自定义收集器,比如统计、分区等:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 统计
IntSummaryStatistics stats = numbers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println(stats.getMax()); // 5
System.out.println(stats.getMin()); // 1
System.out.println(stats.getAverage()); // 3.0
// 分区(按奇偶)
Map<Boolean, List<Integer>> partition = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// 结果:{false=[1,3,5], true=[2,4]}
四、实践与练习:综合案例
我们用一个综合案例,把 Lambda 和 Stream 串起来:
需求:从用户列表中,筛选出年龄大于 18 岁的用户,按年龄排序,收集用户姓名,并用逗号拼接。
class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
public class StreamPractice {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("Alice", 17),
new User("Bob", 20),
new User("Charlie", 16),
new User("David", 22)
);
String result = users.stream()
.filter(user -> user.getAge() > 18) // 过滤年龄>18的用户
.sorted(Comparator.comparingInt(User::getAge)) // 按年龄排序
.map(User::getName) // 映射为用户名
.collect(Collectors.joining(",")); // 拼接字符串
System.out.println(result); // 输出:Bob,David
}
}
总结
Lambda 表达式和 Stream 流处理,是 Java 迈向现代编程的重要一步:
- Lambda 表达式简化了函数式接口的实现,让代码更简洁;
- 方法引用是 Lambda 的进阶缩写,让代码更易读;
- Stream 流处理提供了声明式的集合操作方式,支持链式调用和并行处理;
- Optional 类解决了空指针异常的痛点,让代码更健壮。