Java 8 中最核心和常用的函数式接口及其使用
Java 8 在 java.util.function 包中引入了众多内置的函数式接口。所谓函数式接口(Functional Interface),就是只包含一个抽象方法的接口。你可以使用 @FunctionalInterface 注解来标记它,编译器会帮你检查是否符合规则。
1. Runnable - 无参无返回值
所在包: java.lang抽象方法: void run()用途: 通常用于定义在线程中执行的任务,或者任何不需要参数和返回值的操作。
使用示例:
java
// 传统匿名内部类方式
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
};
// Lambda 表达式方式
Runnable r2 = () -> System.out.println("Hello Lambda!");
// 执行
new Thread(r1).start();
new Thread(r2).start();
new Thread(() -> System.out.println("直接传入Lambda")).start();2. Consumer<T> - 消费型接口
所在包: java.util.function抽象方法: void accept(T t)用途: 接收一个参数,并对其进行某种操作(消费),不返回任何结果。
常用变体:
BiConsumer<T, U>: 接收两个参数。IntConsumer: 接收一个int参数,避免装箱拆箱。
使用示例:
java
// 定义一个消费字符串的Consumer
Consumer<String> printConsumer = (str) -> System.out.println(str);
printConsumer.accept("Hello Consumer!"); // 输出: Hello Consumer!
// 集合的 forEach 方法就接收一个Consumer
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println("Hello, " + name));
// 使用方法引用更简洁: names.forEach(System.out::println);
// 使用 andThen 进行组合消费
Consumer<String> c1 = s -> System.out.print("Length: ");
Consumer<String> c2 = s -> System.out.println(s.length());
Consumer<String> combined = c1.andThen(c2);
combined.accept("Java"); // 输出: Length: 43. Supplier<T> - 供给型接口
所在包: java.util.function抽象方法: T get()用途: 不需要参数,返回一个结果。类似于“工厂”或“提供者”。
常用变体:
BooleanSupplier,IntSupplier,LongSupplier,DoubleSupplier
使用示例:
java
// 定义一个生成随机数的Supplier
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println(randomSupplier.get()); // 输出一个随机数
// 延迟执行/懒加载的经典场景
public static String getExpensiveValue() {
// 假设这是一个耗时操作
return "Expensive Value";
}
public static String lazyGet(Supplier<String> supplier) {
if (someCondition) { // 只有在某些条件下才真正调用获取值的方法
return supplier.get();
}
return "Default Value";
}
// 调用 lazyGet 时,并不会立即执行 getExpensiveValue 方法
// 只有当 supplier.get() 被调用时才会执行
String result = lazyGet(() -> getExpensiveValue());4. Predicate<T> - 断言型接口
所在包: java.util.function抽象方法: boolean test(T t)用途: 接收一个参数,返回一个布尔值。常用于过滤、条件判断。
常用变体:
BiPredicate<T, U>: 接收两个参数。IntPredicate: 接收一个int参数。
默认方法:
and(Predicate other): 与or(Predicate other): 或negate(): 非
使用示例:
java
// 定义一个判断字符串是否为空的Predicate
Predicate<String> isEmpty = String::isEmpty; // s -> s.isEmpty()
System.out.println(isEmpty.test("")); // true
System.out.println(isEmpty.test("Hi")); // false
// 集合的 stream().filter() 方法就接收一个Predicate
List<String> list = Arrays.asList("Java", "Python", "C", "", "JavaScript");
List<String> nonEmpty = list.stream()
.filter(str -> !str.isEmpty()) // 过滤掉空字符串
.collect(Collectors.toList());
// 输出: [Java, Python, C, JavaScript]
// 组合使用:判断字符串长度是否大于3并且不是空字符串
Predicate<String> longerThan3 = s -> s.length() > 3;
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> combinedPredicate = notEmpty.and(longerThan3);
List<String> result = list.stream()
.filter(combinedPredicate)
.collect(Collectors.toList());
// 输出: [Java, Python, JavaScript]5. Function<T, R> - 函数型接口
所在包: java.util.function抽象方法: R apply(T t)用途: 接收一个参数(类型 T),返回一个结果(类型 R)。用于类型的转换或映射。
常用变体:
BiFunction<T, U, R>: 接收两个参数 (T, U),返回 R。UnaryOperator<T>:Function<T, T>的特例,接收 T 返回 T。BinaryOperator<T>:BiFunction<T, T, T>的特例,接收两个 T 返回一个 T。IntFunction<R>,ToIntFunction<T>等:用于基本类型,提高效率。
默认方法:
compose(Function before): 先执行 before 的 apply,再执行当前 apply。andThen(Function after): 先执行当前 apply,再执行 after 的 apply。
使用示例:
java
// 定义一个将字符串转换为整数的Function
Function<String, Integer> parseInt = Integer::parseInt; // s -> Integer.parseInt(s)
Integer number = parseInt.apply("123"); // 返回 123
// 集合的 stream().map() 方法就接收一个Function
List<String> names = Arrays.asList("alice", "bob", "charlie");
List<String> upperCaseNames = names.stream()
.map(name -> name.toUpperCase()) // 将每个元素映射为大写
.collect(Collectors.toList());
// 输出: [ALICE, BOB, CHARLIE]
// 使用方法引用: .map(String::toUpperCase)
// 组合使用:先乘以2,然后再将结果平方
Function<Integer, Integer> timesTwo = x -> x * 2;
Function<Integer, Integer> squared = x -> x * x;
Function<Integer, Integer> composed = timesTwo.andThen(squared);
Integer result = composed.apply(3); // (3 * 2) ^ 2 = 36
System.out.println(result);
// UnaryOperator 示例 (Function<String, String>)
UnaryOperator<String> greet = s -> "Hello, " + s;
System.out.println(greet.apply("World")); // Hello, World6. Callable<V> - 可调用接口
所在包: java.util.concurrent抽象方法: V call() throws Exception用途: 类似于 Runnable,但它可以返回值并且可以抛出受检异常。通常用于 ExecutorService 提交有返回值的任务。
使用示例:
java
// 传统方式
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
// 模拟一些工作
Thread.sleep(1000);
return "Result from Callable";
}
};
// Lambda 表达式方式 (因为只有一个抽象方法,所以可以使用)
Callable<String> callableWithLambda = () -> {
Thread.sleep(1000);
return "Result from Lambda Callable";
};
// 配合线程池使用
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(callableWithLambda);
// 获取结果(会阻塞)
String result = future.get();
System.out.println(result); // 输出: Result from Lambda Callable
executor.shutdown();总结与最佳实践
| 接口 | 方法 | 用途 | 典型场景 |
|---|---|---|---|
Consumer<T> | void accept(T t) | 消费一个参数 | forEach, 打印, 数据持久化 |
Supplier<T> | T get() | 提供一个结果 | 懒加载, 工厂方法, 生成配置 |
Predicate<T> | boolean test(T t) | 判断条件 | filter, 数据校验, 条件检查 |
Function<T, R> | R apply(T t) | 转换/映射一个值 | map, 类型转换, 计算衍生值 |
Runnable | void run() | 执行一个动作 | 线程任务, 无参无返回的操作 |
Callable<V> | V call() | 执行并返回结果 | 线程池提交有返回值的任务 |
最佳实践:
- 优先使用内置接口: 在绝大多数情况下,都应优先使用这些内置接口,而不是自己重新定义。
- 使用方法引用: 当 Lambda 表达式仅仅是调用一个已有方法时,用方法引用(如
System.out::println,String::length)会使代码更简洁。 - 注意组合操作: 利用
Predicate,Function,Consumer提供的and,or,negate,andThen,compose等方法,可以将简单的函数组合成更复杂的逻辑,让代码更具声明性和可读性。 - 区分有无异常:
Runnable和Callable的主要区别在于后者能抛出受检异常并有返回值。根据你的任务需求选择。