Java 内置函数式接口
在学习完Lambda表达式的基础知识后,我们需要进一步了解Java提供的内置函数式接口。这些接口位于java.util.function
包中,它们为Lambda表达式提供了标准的目标类型,极大地简化了函数式编程的实现。
什么是函数式接口?
函数式接口是只包含一个抽象方法的接口。Java 8引入了@FunctionalInterface
注解来标记函数式接口,虽然这个注解不是必须的,但它可以帮助编译器验证接口是否符合函数式接口的要求。
函数式接口的核心特点是:只有一个抽象方法。这使得Lambda表达式可以直接映射到这个方法上。
常用的内置函数式接口
Java提供了多种内置函数式接口,我们将介绍最常用的几个:
1. Function<T, R>
Function
接口表示接受一个参数并产生一个结果的函数。
核心方法:
R apply(T t)
示例:
import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
// 定义一个Function,将输入的字符串转换为其长度
Function<String, Integer> lengthFunction = s -> s.length();
// 使用apply方法应用函数
String input = "Hello, Function!";
Integer length = lengthFunction.apply(input);
System.out.println("字符串: " + input);
System.out.println("长度: " + length);
// 使用andThen方法组合函数
Function<Integer, String> intToString = i -> "长度是: " + i;
String result = lengthFunction.andThen(intToString).apply(input);
System.out.println(result);
}
}
输出:
字符串: Hello, Function!
长度: 16
长度是: 16
2. Consumer<T>
Consumer
接口表示接受单个输入参数但不返回结果的操作。
核心方法:
void accept(T t)
示例:
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
// 定义一个Consumer,打印字符串
Consumer<String> printConsumer = s -> System.out.println("打印: " + s);
// 使用accept方法
printConsumer.accept("Hello, Consumer!");
// 在集合上使用forEach方法(接受一个Consumer参数)
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(printConsumer);
// 链式操作
Consumer<String> upperCaseConsumer = s -> System.out.println(s.toUpperCase());
names.forEach(printConsumer.andThen(upperCaseConsumer));
}
}
输出:
打印: Hello, Consumer!
打印: Alice
打印: Bob
打印: Charlie
打印: Alice
ALICE
打印: Bob
BOB
打印: Charlie
CHARLIE
3. Supplier<T>
Supplier
接口表示不接受参数但返回结果的函数。
核心方法:
T get()
示例:
import java.time.LocalDateTime;
import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
// 定义一个Supplier,提供当前时间
Supplier<LocalDateTime> timeSupplier = () -> LocalDateTime.now();
// 使用get方法获取结果
LocalDateTime time = timeSupplier.get();
System.out.println("当前时间: " + time);
// 延迟执行的示例
System.out.println("等待2秒...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再次获取时间
System.out.println("新的时间: " + timeSupplier.get());
// 创建随机数的Supplier
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println("随机数: " + randomSupplier.get());
System.out.println("另一个随机数: " + randomSupplier.get());
}
}
输出:
当前时间: 2023-06-15T14:30:45.123
等待2秒...
新的时间: 2023-06-15T14:30:47.124
随机数: 0.7564892498710233
另一个随机数: 0.12738901287340982
4. Predicate<T>
Predicate
接口表示接受一个参数并返回布尔值的函数。
核心方法:
boolean test(T t)
示例:
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PredicateExample {
public static void main(String[] args) {
// 定义一个Predicate,检查字符串长度是否大于5
Predicate<String> lengthGreaterThan5 = s -> s.length() > 5;
// 使用test方法
String testStr = "Hello, World!";
boolean result = lengthGreaterThan5.test(testStr);
System.out.println("字符串 '" + testStr + "' 长度大于5: " + result);
// 在集合上使用filter方法(接受一个Predicate参数)
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave", "Elizabeth");
List<String> longNames = names.stream()
.filter(lengthGreaterThan5)
.collect(Collectors.toList());
System.out.println("长度大于5的名字: " + longNames);
// 组合Predicate
Predicate<String> startsWithA = s -> s.startsWith("A");
Predicate<String> longNameStartsWithA = lengthGreaterThan5.and(startsWithA);
System.out.println("长度大于5且以'A'开头的名字: " +
names.stream()
.filter(longNameStartsWithA)
.collect(Collectors.toList()));
}
}
输出:
字符串 'Hello, World!' 长度大于5: true
长度大于5的名字: [Charlie, Elizabeth]
长度大于5且以'A'开头的名字: []
5. BiFunction<T, U, R>
BiFunction
接口表示接受两个参数并产生一个结果的函数。
核心方法:
R apply(T t, U u)
示例:
import java.util.function.BiFunction;
public class BiFunctionExample {
public static void main(String[] args) {
// 定义一个BiFunction,求两个数的和
BiFunction<Integer, Integer, Integer> adder = (a, b) -> a + b;
// 使用apply方法
int sum = adder.apply(10, 25);
System.out.println("10 + 25 = " + sum);
// 定义一个拼接字符串的BiFunction
BiFunction<String, String, String> combiner = (s1, s2) -> s1 + s2;
String combined = combiner.apply("Hello, ", "BiFunction!");
System.out.println(combined);
// 结合andThen使用
BiFunction<Integer, Integer, String> sumToString = adder.andThen(n -> "结果是: " + n);
System.out.println(sumToString.apply(100, 200));
}
}
输出:
10 + 25 = 35
Hello, BiFunction!
结果是: 300
其他常用的函数式接口
除了上述主要接口外,Java还提供了其他许多专用接口:
BiConsumer<T, U>
:接受两个参数,不返回结果BiPredicate<T, U>
:接受两个参数,返回布尔值UnaryOperator<T>
:接受一个T类型参数,返回T类型结果BinaryOperator<T>
:接受两个T类型参数,返回T类型结果IntFunction<R>
,LongFunction<R>
,DoubleFunction<R>
:接受原始类型参数,返回泛型结果ToIntFunction<T>
,ToLongFunction<T>
,ToDoubleFunction<T>
:接受泛型参数,返回原始类型结果
实际应用场景
让我们看一个更复杂的实际应用场景,结合多种函数式接口:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
class Product {
private String name;
private double price;
private String category;
public Product(String name, double price, String category) {
this.name = name;
this.price = price;
this.category = category;
}
public String getName() { return name; }
public double getPrice() { return price; }
public String getCategory() { return category; }
@Override
public String toString() {
return name + " ($" + price + ")";
}
}
public class ShoppingCartExample {
public static void main(String[] args) {
// 创建产品目录
List<Product> catalog = new ArrayList<>();
catalog.add(new Product("笔记本电脑", 6999.99, "电子产品"));
catalog.add(new Product("手机", 2999.99, "电子产品"));
catalog.add(new Product("衬衫", 199.99, "服装"));
catalog.add(new Product("裤子", 299.99, "服装"));
catalog.add(new Product("鞋子", 499.99, "鞋类"));
// 1. 使用Predicate过滤产品
Predicate<Product> isElectronic = p -> p.getCategory().equals("电子产品");
Predicate<Product> isExpensive = p -> p.getPrice() > 1000;
// 2. 使用Function转换数据
Function<Product, String> productToName = p -> p.getName();
// 3. 使用Consumer显示产品
Consumer<Product> displayProduct = p ->
System.out.println("产品: " + p.getName() + ", 价格: $" + p.getPrice());
// 4. 使用BiFunction计算折扣价格
BiFunction<Product, Double, Double> applyDiscount =
(product, discountPercent) -> product.getPrice() * (1 - discountPercent/100);
// 5. 使用Supplier创建购物车
Supplier<List<Product>> createCart = () -> new ArrayList<>();
// 电子产品应用场景
System.out.println("-----电子产品-----");
catalog.stream()
.filter(isElectronic)
.forEach(displayProduct);
// 贵重产品应用场景
System.out.println("\n-----贵重产品-----");
catalog.stream()
.filter(isExpensive)
.forEach(displayProduct);
// 计算折扣价格
System.out.println("\n-----电子产品打折(20%)-----");
catalog.stream()
.filter(isElectronic)
.forEach(p -> System.out.println(
p.getName() + " 原价: $" + p.getPrice() +
", 折扣后: $" + applyDiscount.apply(p, 20.0)));
// 创建购物车并添加产品
List<Product> cart = createCart.get();
cart.add(catalog.get(0)); // 添加笔记本电脑
cart.add(catalog.get(2)); // 添加衬衫
// 计算购物车总价
double total = cart.stream()
.mapToDouble(Product::getPrice)
.sum();
System.out.println("\n-----购物车-----");
cart.forEach(displayProduct);
System.out.println("总价: $" + total);
}
}
输出:
-----电子产品-----
产品: 笔记本电脑, 价格: $6999.99
产品: 手机, 价格: $2999.99
-----贵重产品-----
产品: 笔记本电脑, 价格: $6999.99
产品: 手机, 价格: $2999.99
-----电子产品打折(20%)-----
笔记本电脑 原价: $6999.99, 折扣后: $5599.992
手机 原价: $2999.99, 折扣后: $2399.992
-----购物车-----
产品: 笔记本电脑, 价格: $6999.99
产品: 衬衫, 价格: $199.99
总价: $7199.98
原始类型函数式接口
为了避免基本数据类型的装箱和拆箱操作,Java提供了针对原始类型的特殊函数式接口:
import java.util.function.IntPredicate;
import java.util.function.IntFunction;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;
public class PrimitiveInterfaceExample {
public static void main(String[] args) {
// IntPredicate - 避免装箱/拆箱
IntPredicate isEven = n -> n % 2 == 0;
System.out.println("10是偶数: " + isEven.test(10));
// IntFunction - 接受int参数,返回泛型结果
IntFunction<String> intToString = n -> "数字: " + n;
System.out.println(intToString.apply(42));
// IntConsumer - 接受int参数,无返回值
IntConsumer printSquare = n -> System.out.println(n + "的平方是: " + (n * n));
printSquare.accept(5);
// 使用IntStream
System.out.println("\n1-5的偶数:");
IntStream.rangeClosed(1, 5)
.filter(isEven)
.forEach(printSquare);
}
}
输出:
10是偶数: true
数字: 42
5的平方是: 25
1-5的偶数:
2的平方是: 4
4的平方是: 16
最佳实践
使用内置函数式接口时,可以遵循以下最佳实践:
-
选择正确的接口:根据您的需求选择最合适的接口。例如,如果需要接受一个参数并返回结果,使用
Function
;如果只是消费数据不返回结果,使用Consumer
。 -
利用链式操作:大多数函数式接口都提供了组合操作的方法,如
andThen
、compose
等。 -
利用方法引用:如果Lambda表达式只是调用一个方法,可以使用方法引用简化代码。例如,
s -> s.length()
可以简化为String::length
。 -
避免副作用:函数式编程的核心原则是避免副作用,尽量让您的Lambda表达式保持纯函数的特性。
-
使用原始类型专用接口:处理大量原始数据类型时,使用
IntFunction
、LongConsumer
等专用接口以提高性能。
总结
Java内置函数式接口是Java 8引入的函数式编程支持的重要组成部分。它们为Lambda表达式提供了标准的目标类型,使得函数式编程在Java中变得更加简洁和强大。
主要内置函数式接口包括:
Function<T, R>
:接受一个输入并产生一个结果Consumer<T>
:接受一个输入但不返回任何结果Supplier<T>
:不接受输入但产生一个结果Predicate<T>
:接受一个输入并返回布尔值BiFunction<T, U, R>
:接受两个输入并产生一个结果- 以及其他专用接口
通过理解和熟练使用这些接口,您可以编写更简洁、更高效的代码,并充分利用Java的函数式编程特性。
练习
- 创建一个
Function
,将字符串转换为整数,然后使用andThen
将结果增加10。 - 创建一个
Predicate
,检查一个字符串是否是回文(正读反读相同)。 - 使用
Consumer
遍历一个字符串列表并打印每个元素的长度。 - 创建一个
Supplier
,生成一个包含10个随机数的列表。 - 使用
BiFunction
实现一个简单的计算器,能够执行加、减、乘、除操作。
附加资源
通过掌握这些内置函数式接口,您将能够更好地利用Java 8+提供的函数式编程能力,编写更加简洁、可维护的代码。