Lambda 表达式
面试提问
"Lambda 表达式是什么?函数式接口有哪些?"
Lambda 表达式简介
Lambda 表达式是 Java 8 引入的重要特性,允许将函数作为方法参数传递。
语法
// 基本语法
(参数列表) -> { 方法体 }
// 示例
(String s) -> s.length()
(int x, int y) -> x + y
() -> 42对比传统写法
// 传统匿名内部类
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// Lambda 表达式
Runnable r2 = () -> System.out.println("Hello");
// 传统写法
Comparator<String> c1 = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};
// Lambda 表达式
Comparator<String> c2 = (a, b) -> a.length() - b.length();语法精简规则
1. 参数类型可省略
// 完整写法
(String s) -> s.length()
// 省略类型
s -> s.length()2. 单参数可省略括号
// 完整写法
(s) -> s.length()
// 省略括号
s -> s.length()3. 方法体单语句可省略花括号和 return
// 完整写法
s -> { return s.length(); }
// 省略写法
s -> s.length()函数式接口
定义
只包含一个抽象方法的接口称为函数式接口,可用 @FunctionalInterface 注解标记。
@FunctionalInterface
public interface Runnable {
void run();
}Java 内置函数式接口
| 接口 | 参数 | 返回 | 用途 |
|---|---|---|---|
Runnable | 无 | void | 执行操作 |
Supplier | 无 | T | 供给数据 |
Consumer | T | void | 消费数据 |
Function<T,R> | T | R | 转换数据 |
Predicate | T | boolean | 判断条件 |
BiFunction<T,U,R> | T, U | R | 双参数转换 |
BinaryOperator | T, T | T | 双参数同类型运算 |
UnaryOperator | T | T | 单参数同类型运算 |
使用示例
// Supplier:无参有返回
Supplier<String> supplier = () -> "Hello";
String s = supplier.get(); // "Hello"
// Consumer:有参无返回
Consumer<String> consumer = str -> System.out.println(str);
consumer.accept("World"); // 打印 "World"
// Function:有参有返回
Function<String, Integer> function = str -> str.length();
int len = function.apply("Hello"); // 5
// Predicate:有参返回 boolean
Predicate<String> predicate = str -> str.length() > 3;
boolean result = predicate.test("Hello"); // true
// BinaryOperator:两个同类型参数返回同类型
BinaryOperator<Integer> add = (a, b) -> a + b;
int sum = add.apply(1, 2); // 3方法引用
方法引用是 Lambda 表达式的简写形式,使用 :: 操作符。
四种形式
| 类型 | 语法 | 示例 |
|---|---|---|
| 静态方法引用 | 类名::静态方法 | Math::abs |
| 实例方法引用 | 对象::实例方法 | System.out::println |
| 类型方法引用 | 类名::实例方法 | String::length |
| 构造器引用 | 类名::new | ArrayList::new |
示例
// 静态方法引用
Function<Integer, Integer> f1 = x -> Math.abs(x);
Function<Integer, Integer> f2 = Math::abs;
// 实例方法引用
Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;
// 类型方法引用(第一个参数作为调用者)
Function<String, Integer> f3 = s -> s.length();
Function<String, Integer> f4 = String::length;
// 构造器引用
Supplier<List<String>> s1 = () -> new ArrayList<>();
Supplier<List<String>> s2 = ArrayList::new;
// 带参数的构造器引用
Function<Integer, List<String>> f5 = n -> new ArrayList<>(n);
Function<Integer, List<String>> f6 = ArrayList::new;Lambda 与变量作用域
访问局部变量
Lambda 可以访问外部的局部变量,但必须是 effectively final(事实上不可变)。
String prefix = "Hello, "; // effectively final
Consumer<String> consumer = name -> {
System.out.println(prefix + name); // 可以访问
// prefix = "Hi, "; // 编译错误:不能修改
};
// prefix = "Hi, "; // 编译错误:修改后 Lambda 无法访问this 引用
Lambda 内部的 this 指向外部类,而匿名内部类的 this 指向匿名类本身。
public class Test {
public void run() {
// Lambda:this 指向 Test
Runnable r1 = () -> System.out.println(this.getClass()); // Test
// 匿名内部类:this 指向匿名类
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println(this.getClass()); // Test$1
}
};
}
}实际应用
集合排序
List<String> list = Arrays.asList("banana", "apple", "cherry");
// 传统写法
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
// Lambda
Collections.sort(list, (a, b) -> a.compareTo(b));
// 方法引用
Collections.sort(list, String::compareTo);
// List.sort()
list.sort(String::compareTo);集合遍历
List<String> list = Arrays.asList("a", "b", "c");
// 传统 for 循环
for (String s : list) {
System.out.println(s);
}
// Lambda
list.forEach(s -> System.out.println(s));
// 方法引用
list.forEach(System.out::println);条件过滤
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 过滤偶数
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// [2, 4, 6]Lambda vs 匿名内部类
| 对比项 | Lambda | 匿名内部类 |
|---|---|---|
| 代码简洁度 | 简洁 | 冗长 |
| this 指向 | 外部类 | 匿名类本身 |
| 可访问变量 | effectively final | effectively final |
| 可实现接口 | 仅函数式接口 | 任意接口 |
| 可继承类 | 不可以 | 可以(抽象类) |
| 编译产物 | invokedynamic | 生成类文件 |
面试要点总结
| 问题 | 答案要点 |
|---|---|
| Lambda 是什么? | 匿名函数,可传递的代码块 |
| 函数式接口? | 只有一个抽象方法的接口 |
| 常用函数式接口? | Supplier、Consumer、Function、Predicate |
| 方法引用形式? | 静态方法、实例方法、类型方法、构造器 |
| 变量访问限制? | 外部变量必须是 effectively final |
参考资料
- Java Lambda 表达式 (opens in a new tab)
- 《Java 8 实战》