函数式接口
想象这样一个场景:你需要传递一段行为——不是数据,不是对象,而是一段可执行的逻辑。
在 Java 8 之前,你得这样写:
java
// 创建一个线程,执行一段代码
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
});
t.start();匿名内部类写起来繁琐,代码比意图更显眼。
有了函数式接口和 Lambda 表达式,同样的逻辑可以这样写:
java
Thread t = new Thread(() -> System.out.println("Hello"));
t.start();简洁到只剩核心逻辑。
什么是函数式接口
函数式接口是只有一个抽象方法的接口。Runnable 是最经典的例子:
java
@FunctionalInterface // 可选,但建议加上
public interface Runnable {
void run(); // 只有一个抽象方法
}@FunctionalInterface 注解不是必须的,但加上后,编译器会帮你检查「这个接口是否真的只有一个抽象方法」。
Lambda 表达式
Lambda 表达式是函数式接口的简洁实现方式。
基本语法
java
// 完整写法
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// Lambda 写法
Runnable r2 = () -> System.out.println("Hello");语法规则:
(参数) -> { 方法体 }
() -> { System.out.println("Hello"); } // 无参数
x -> { return x * 2; } // 单参数(括号可省略)
(x, y) -> { return x + y; } // 多参数省略规则
java
// 参数类型可省略(编译器推断)
Comparator<String> c1 = (String a, String b) -> a.length() - b.length();
Comparator<String> c2 = (a, b) -> a.length() - b.length(); // 类型省略
// 单参数可省略括号
Consumer<String> c3 = (String s) -> System.out.println(s);
Consumer<String> c4 = s -> System.out.println(s); // 括号省略
// 单行方法体可省略花括号和 return
Function<Integer, Integer> f1 = (Integer x) -> { return x * 2; };
Function<Integer, Integer> f2 = x -> x * 2; // 简写内置函数式接口
JDK 8 提供了一组常用的函数式接口,在 java.util.function 包中:
| 接口 | 抽象方法 | 说明 | 示例 |
|---|---|---|---|
Runnable | void run() | 无参数无返回值 | () -> System.out.println("hi") |
Supplier<T> | T get() | 无参数有返回值 | () -> "hello" |
Consumer<T> | void accept(T t) | 消费参数,无返回值 | s -> System.out.println(s) |
Function<T,R> | R apply(T t) | 转换,有参数有返回值 | s -> s.length() |
Predicate<T> | boolean test(T t) | 判断,返回布尔值 | s -> s.isEmpty() |
BiConsumer<T,U> | void accept(T t, U u) | 消费两个参数 | (a, b) -> System.out.println(a + b) |
BiFunction<T,U,R> | R apply(T t, U u) | 处理两个参数 | (a, b) -> a + b |
UnaryOperator<T> | T apply(T t) | 一元操作(参数返回同类) | x -> x + 1 |
BinaryOperator<T> | T apply(T t1, T t2) | 二元操作 | (a, b) -> a + b |
基本类型特化
为了避免装箱拆箱开销,基本类型有专门的版本:
java
// IntFunction<R>:接收 int,返回 R
IntFunction<String> intToString = i -> String.valueOf(i);
// IntConsumer:接收 int,无返回
IntConsumer printInt = i -> System.out.println(i);
// IntSupplier:无参数,返回 int
IntSupplier randomInt = () -> new Random().nextInt();
// IntPredicate:接收 int,返回 boolean
IntPredicate isEven = i -> i % 2 == 0;
// ToIntFunction<T>:接收 T,返回 int
ToIntFunction<String> toLength = s -> s.length();实战应用
集合排序
java
List<String> names = Arrays.asList("Tom", "Alice", "Bob");
// 匿名内部类
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
// Lambda 表达式
names.sort((s1, s2) -> s1.length() - s2.length());
// 方法引用(更简洁)
names.sort(Comparator.comparingInt(String::length));集合遍历
java
List<String> list = Arrays.asList("a", "b", "c");
// forEach + Lambda
list.forEach(s -> System.out.println(s));
// 方法引用
list.forEach(System.out::println);过滤器
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 过滤偶数
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 过滤并转换
List<String> names = numbers.stream()
.filter(n -> n > 3)
.map(Object::toString)
.collect(Collectors.toList());延迟执行
java
// 定义行为,不立即执行
Supplier<User> userSupplier = () -> fetchUserFromDatabase();
// 需要时才执行
if (needUser) {
User user = userSupplier.get();
}方法引用
当 Lambda 表达式只调用一个已有方法时,可以进一步简化为方法引用:
java
// Lambda
s -> System.out.println(s)
s -> Integer.parseInt(s)
(a, b) -> a.compareTo(b)
// 方法引用
System.out::println
Integer::parseInt
String::compareTo方法引用的几种形式:
java
// 静态方法引用
ClassName::staticMethod // Integer::parseInt
// 实例方法引用(特定对象)
instance::instanceMethod // str::length
// 实例方法引用(任意对象)
ClassName::instanceMethod // String::toUpperCase
// 构造函数引用
ClassName::new // User::new组合使用
函数式接口可以被组合,形成更复杂的行为:
java
// Predicate 组合
Predicate<String> nonNull = s -> s != null;
Predicate<String> nonBlank = s -> !s.isBlank();
Predicate<String> valid = nonNull.and(nonBlank); // 且
Predicate<String> notValid = nonNull.negate(); // 非
// Function 组合
Function<String, Integer> parse = Integer::parseInt;
Function<Integer, String> toString = Object::toString;
Function<String, String> pipeline = parse.andThen(toString);
pipeline.apply("123"); // "123"
// Comparator 组合
Comparator<String> byLength = Comparator.comparingInt(String::length);
Comparator<String> byAlphabet = Comparator.naturalOrder();
Comparator<String> combined = byLength.thenComparing(byAlphabet);注意事项
1. 不要过度使用
Lambda 不是万能药。当逻辑复杂时,单独的方法或匿名内部类可能更清晰:
java
// ❌ 过度使用 Lambda
list.stream()
.filter(s -> {
if (s == null) return false;
if (s.length() > 10) return false;
return s.startsWith("A");
});
// ✅ 提取为方法
list.stream()
.filter(this::isValidName);
// 或者用匿名内部类(逻辑复杂时)
list.stream()
.filter(new Predicate<String>() {
@Override
public boolean test(String s) {
// 复杂的判断逻辑
}
});2. Lambda 中的变量作用域
java
int base = 10; // effectively final
// base = 20; // 如果放开这行,Lambda 会编译失败
Function<Integer, Integer> add = x -> x + base;Lambda 可以访问外部变量,但该变量必须是 effectively final(只赋值一次)。
3. this 的含义
Lambda 中的 this 指向包含它的类,而不是 Lambda 本身:
java
public class MyClass {
public void doSomething() {
Runnable r = () -> {
// 这里的 this 是 MyClass,不是 Runnable
System.out.println(this);
};
r.run();
}
}总结
函数式接口 = 一个抽象方法的接口
Lambda = 实现函数式接口的简洁写法
方法引用 = Lambda 的进一步简化函数式接口 + Lambda 表达式是 Java 8 最重要的新特性之一。它们让代码变得更简洁,也让「传递行为」变得自然——从这一刻起,Java 真正拥抱了函数式编程的思想。
