functional interface
分类
Java
中有个特殊的注解 @FunctionalInterface
,JDK
中的一些接口带有这个注解,例如
Runnable
Function
Consumer
Supplier
Predicate
这些接口有什么区别呢?本文会讨论这个问题。
什么是 functional interface
?
在 @FunctionalInterface
的源代码中,可以看到它的 javadoc
。 其内容如下图所示 ⬇️
其中有几个要点
@FunctionalInterface
用于表明一个接口是functional interface
- 一个
functional interface
恰有 一个 抽象方法 - 如果一个接口恰有一个抽象方法,那么编译器会将其当作
functional interface
来处理(不管这个接口是否被@FunctionalInterface
注解修饰)
常见 functional interface
的分类
基础分类
JDK
中有一些接口带有 @FunctionalInterface
注解,它们的区别是什么呢? 如上文所述,每个 functional interface
恰有一个抽象方法。 我们从以下两个角度来查看各个接口的特点
- 抽象方法的参数的个数
- 抽象方法是否有返回值
例如对 Runnable
, Function
,Consumer
,Supplier
这四个接口,我们可以进行如下的分类。
参数个数 | 有返回值吗 | 接口 | 代码 | 抽象方法 |
---|---|---|---|---|
0 |
❌ | java.lang.Runnable |
Runnable.java | void run() |
0 |
✅ | java.util.function.Supplier<T> |
Supplier.java | T get() |
1 |
❌ | java.util.function.Consumer<T> |
Consumer.java | void accept(T t) |
1 |
✅ | java.util.function.Function<T, R> |
Function.java | R apply(T t) |
如果忽略掉抽象方法的名称的差异,上表中的 4
个接口的差异仅仅在于参数个数以及是否有返回值。
可以简单将它们概括如下 (例如 Runnable
中的抽象方法的入参是 0
个,没有返回值)⬇️
- <math xmlns="http://www.w3.org/1998/Math/MathML"> R u n n a b l e : ( ) → v o i d Runnable: () \rightarrow void </math>Runnable:()→void
- <math xmlns="http://www.w3.org/1998/Math/MathML"> S u p p l i e r : ( ) → T Supplier: () \rightarrow T </math>Supplier:()→T
- <math xmlns="http://www.w3.org/1998/Math/MathML"> C o n s u m e r : T → v o i d Consumer: T \rightarrow void </math>Consumer:T→void
- <math xmlns="http://www.w3.org/1998/Math/MathML"> F u n c t i o n : T → R Function: T \rightarrow R </math>Function:T→R
用于支持原始类型的 functional interface
在此基础上,可以再看其他的 functional interface
。 对 Predicate.java 而言,这个接口中的 test(T t)
方法,入参的个数是 1
,返回值是 boolean
类型,它和 java.util.function.Function
接口有些相似,两者的差异如下表所示 ⬇️
接口 | 抽象方法 | 返回值类型 |
---|---|---|
java.util.function.Function<T, R> |
R apply(T t) |
R (是引用类型) |
java.util.function.Predicate<T> |
boolean test(T t) |
boolean (是原始类型) |
由于 Function
,Consumer
,Supplier
这三个接口都涉及泛型,所以如果用它们来处理 int/long/double
之类的原始类型,会触发装箱/拆箱操作,这样效率较低,于是 JDK 中又提供了不需要拆箱/装箱操作的,适用于 int/long/double
等原始类型的 functional interface
。
以 int
为例,列举相关 functional interface
以 int
为例,Function
,Consumer
,Supplier
的对应版本分别如下(为了方便做对比,普通的 Function
,Consumer
,Supplier
也列在下表里了) ⬇️
接口 | 代码 | 抽象方法 | 类型转化 | 可以避免 int 和 Integer 之间的装箱/拆箱操作吗? |
---|---|---|---|---|
java.util.function.Function<T, R> |
Function.java | R apply(T t) |
<math xmlns="http://www.w3.org/1998/Math/MathML"> ( T ) → R (T){\rightarrow}R </math>(T)→R | ❌ |
java.util.function.IntFunction |
IntFunction.java | R apply(int value) |
<math xmlns="http://www.w3.org/1998/Math/MathML"> ( i n t ) → R (int){\rightarrow}R </math>(int)→R | ✅ |
java.util.function.ToIntFunction<T> |
ToIntFunction.java | int applyAsInt(T value) |
<math xmlns="http://www.w3.org/1998/Math/MathML"> ( T ) → i n t (T){\rightarrow}int </math>(T)→int | ✅ |
java.util.function.IntUnaryOperator |
IntUnaryOperator.java | int applyAsInt(int operand) |
<math xmlns="http://www.w3.org/1998/Math/MathML"> ( i n t ) → i n t (int){\rightarrow}int </math>(int)→int | ✅ |
java.util.function.Consumer<T> |
Consumer.java | void accept(T t) |
<math xmlns="http://www.w3.org/1998/Math/MathML"> ( T ) → v o i d (T){\rightarrow}void </math>(T)→void | ❌ |
java.util.function.IntConsumer |
IntConsumer.java | void accept(int value) |
<math xmlns="http://www.w3.org/1998/Math/MathML"> ( i n t ) → v o i d (int){\rightarrow}void </math>(int)→void | ✅ |
java.util.function.Supplier<T> |
Supplier.java | T get() |
<math xmlns="http://www.w3.org/1998/Math/MathML"> ( ) → T (){\rightarrow}T </math>()→T | ❌ |
java.util.function.IntSupplier |
IntSupplier.java | int getAsInt() |
<math xmlns="http://www.w3.org/1998/Math/MathML"> ( ) → i n t (){\rightarrow}int </math>()→int | ✅ |
用于支持不同数量的参数的 functional interface
有时候,我们需要处理不止一个入参,那么上面提到的 functional interface
就都不太方便,而 JDK
里提供了可以处理 2
个入参的 functional interface
(如果参数个数多于 2
, 可以通过反复应用这些处理 2
个入参的 functional interface
来完成目标)。 当入参个数为 2 时,Function
,Consumer
对应的接口列举如下(为了方便做对比,普通的 Function
,Consumer
也列在下表里了)⬇️
接口 | 代码 | 抽象方法 | 入参个数 |
---|---|---|---|
java.util.function.Function<T, R> |
Function.java | R apply(T t) |
1 |
java.util.function.BiFunction<T, U, R> |
BiFunction.java | R apply(T t, U u) |
2 |
java.util.function.Consumer<T> |
Consumer.java | void accept(T t) |
1 |
java.util.function.BiConsumer<T, U> |
BiConsumer.java | void accept(T t, U u) |
2 |
注意,由于 java
中不支持返回多个值,所以没有 BiSupplier
这种接口。