什么是函数式接口?
定义:有且仅有一个抽象方法,但可以有多个非抽象方法的接口。
在Java 8中,函数式接口被专门存放于java.util.function包内,并且该包下的所有接口都使用@FunctionalInterface注解进行标记,这是Java 8为了支持函数式编程而引入的一个新特性。
除了java.util.function包中的函数式接口外,其他包中也存在一些函数式接口(例如java.lang.Runnable),其中有些并没有使用 @FunctionalInterface注解进行标注。然而,只要符合函数式接口的定义,这些接口仍然可以被视为函数式接口,与是否有此注解无关。注解的作用仅仅是在编译时起到强制规范定义的作用,提示编译器检查接口是否仅包含一个抽象方法。
四大函数式接口
常见的四大函数式接口为:Function(函数型接口)、Predicate(断言型接口)、Consumer(消费型接口)与Supplier(供给型接口)。
Function(函数型接口)
接收一个参数并产生结果,其函数描述符为T -> R
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
public static void main(String[] args) {
// 写法一
Function<String, String> f1 = new Function<String, String>(){
@Override
public String apply(String x) {
return x.toUpperCase();
}
};
System.out.println(f1.apply("f1 hello"));
// 写法二
Function<String, String> f2 = x -> x.toUpperCase();
System.out.println(f2.apply("f2 hello"));
// 写法三
Function<String, String> f3 = String::toUpperCase;
System.out.println(f3.apply("f3 hello"));
}
输出结果:
F1 HELLO
F2 HELLO
F3 HELLO
Predicate(断言型接口)
接收一个参数返回布尔值,其函数描述符为T -> boolean
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
public static void main(String[] args) {
// 写法一
Predicate<Integer> p1 = new Predicate<Integer>() {
@Override
public boolean test(Integer num) {
return num % 2 == 0;
}
};
System.out.println(p1.test(2));
// 写法二
Predicate<Integer> p2 = num -> num % 2 == 0;
System.out.println(p2.test(2));
}
输出结果:
true
true
Consumer(消费型接口)
接收一个参数无返回值,其函数描述符为T -> void
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
public static void main(String[] args) {
// 写法一
Consumer<String> c1 = new Consumer<String>() {
@Override
public void accept(String x) {
System.out.println(x);
}
};
c1.accept("c1 hello");
// 写法二 Lambda
Consumer<String> c2 = x -> System.out.println(x);
c1.accept("c2 hello");
// 写法三 方法引用
Consumer<String> c3 = System.out::println;
c1.accept("c3 hello");
}
输出结果:
c1 hello
c2 hello
c3 hello
Supplier(供给型接口)
无参数,只有返回值,其函数描述符为()-> T
@FunctionalInterface
public interface Supplier<T> {
T get();
}
public static void main(String[] args) {
// 写法一
Supplier<Integer> s1 = new Supplier<Integer>() {
@Override
public Integer get() {
return 10;
}
};
System.out.println(s1.get());
// 写法二
Supplier<Integer> s2 = () -> 10;
System.out.println(s2.get());
}
控制台输出结果如下:
10
10
为什么Java引入函数式接口?
函数式接口的引入主要是为了支持行为参数化,即允许将代码作为数据传递。
具体来说,函数式接口的引入有以下几个原因和优势:
- 简化代码: 使用函数式接口可以大幅减少代码量,使代码更加简洁易读。Lambda表达式和方法引用提供了一种更简洁的方式来实现接口。
- 提高灵活性: 函数式接口使得在设计API时可以更加灵活地接受不同的行为参数,这在设计可配置和可扩展的系统时非常有用。
- 性能优化: 在某些情况下,使用函数式接口可以带来性能上的优化,因为它们通常会被编译器优化为更高效的形式。
综上所述,函数式接口的引入为Java开发者提供了一种新的编程范式,使得代码更加简洁、灵活和高效。