java本身是一个面向对象的语言,但在后来的函数式编程的逐渐兴起,java也不得不把函数式编程引入进来。本文主要介绍函数式接口的相关内容。
@FunctionalInterface注解
在读java源码的过程中,发现很多接口都会有@FunctionalInterface注解,例如Predicate、Function、Consumer等等。这些接口都有一个共同的特点,就是只包含一个抽象方法。该注解的作用也是如此:
- 确保接口只有一个抽象方法,多个会编译错误。
- 表明该接口用于了Lambda表达式。
- 表明是函数式接口。
函数式接口的作用和意义是什么?
函数式接口的作用和意义主要体现在它们能够让程序员更简洁地写出可复用、可组合的代码。以 Predicate 这个函数式接口举例:
Predicate<T> 定义了一个抽象方法 test(T t),用于传入一个参数并返回一个布尔值。它通常用于集合过滤、条件判断等场景。
class User {
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class PredicateExample {
public static void main(String[] args) {
List<User> users = List.of(
new User("Alice", 23),
new User("Bob", 30),
new User("Charlie", 35),
new User("David", 40)
);
// 定义一个 Predicate 来测试用户年龄是否大于25
Predicate<User> isOlderThan25 = user -> user.age > 25;
// 使用 Predicate 进行过滤
List<User> olderUsers = users.stream()
.filter(isOlderThan25)
.collect(Collectors.toList());
System.out.println(olderUsers);
}
}
简洁性: 通过 Lambda 表达式和函数式接口的组合,我们可以简化写出过滤逻辑,而不需显式地定义实现类。Predicate 的 test 方法由一个简单的表达式实现,使得代码简洁直观。
可复用性: Predicate<User> 的定义是独立的,如果我们需要在其他地方使用这个条件,只需传递这个对象即可。这样的设计鼓励代码复用。
组合能力: Predicate 提供了方法如 and、or 和 negate 来组合多个条件。例如,如果我们需要找到年龄大于 25 且名字以字母 ‘C’ 开头的用户,可以通过组合 Predicate 来实现
函数式接口为什么只能有一个抽象方法?
其实从上面的代码例子已经可以明确的看出了,函数式接口之所以只能有一个抽象方法,是因为它们的设计是为了支持 Java 的 lambda 表达式,而 lambda 表达式本身是对匿名函数的一种简洁表示。因此,函数式接口的核心目的是要提供一种明确的方法,让 lambda 表达式能够与之匹配。 当使用 lambda 表达式时,Java 编译器会根据上下文来推断其类型并匹配函数式接口。如果接口有多个抽象方法,编译器就无法确定应该对应哪个方法。这会引发混淆并导致编译错误。同时,也为程序员提供了一种明确的契约。实现类或者实例知道其唯一使命是什么,并避免多重实现带来的接口复杂性。