函数接口,你可以理解为对一段行为的抽象,简单点说可以在方法就是将一段行为作为参数进行传递,这个行为呢,可以是一段代码,也可以是一个方法,那你可以想象在java8之前要将一段方法作为参数传递只能通过匿名内部类来实现,而且代码很难看,也很长,函数接口就是对匿名内部类的优化。
虽然类库中的基本函数接口特别多,但其实总体可以分成四类,就好像阿拉伯数字是无限多的,但总共就10个基本数字一样,理解了这4个,其他的就都明白了。
Function接口
Function的作用是:实现一个”一元函数“,即传入一个值经过函数的计算返回另一个值。在Function中,T代表输入参数,R代表返回的结果。类似于函数
y
=
f
(
x
)
y=f(x)
y=f(x)。
源码解析
R apply(T t);
举个例子更好明白 :
public void test(){
Function<Integer,Integer> test=i->i+1;
test.apply(5);
}
/** print:6*/
我们用lambda表达式定义了一个行为使得i自增1,我们使用参数5执行apply,最后返回6。
这跟我们以前看待Java的眼光已经不同了,在函数式编程之前我们定义一组操作首先想到的是定义一个方法,然后指定传入参数,返回我们需要的结果。函数式编程的思想是先不去考虑具体的行为,而是先去考虑参数,具体的方法我们可以后续再设置。
public void test(){
Function<Integer,Integer> test1=i->i+1;
Function<Integer,Integer> test2=i->i*i;
System.out.println(calculate(test1,5));
System.out.println(calculate(test2,5));
}
public static Integer calculate(Function<Integer,Integer> test,Integer number){
return test.apply(number);
}
/** print:6*/
/** print:25*/
compose和andThen可以解决我们的问题。先看compose的源码:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
compose接收一个Function参数,返回时先用传入的逻辑执行apply,然后使用当前Function的apply。
andThen跟compose正相反,先执行当前的逻辑,再执行传入的逻辑。
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
举个例子:
public void test(){
Function<Integer,Integer> A=i->i+1;
Function<Integer,Integer> B=i->i*i;
System.out.println("F1:"+B.apply(A.apply(5)));
System.out.println("F1:"+B.compose(A).apply(5));
System.out.println("F2:"+A.apply(B.apply(5)));
System.out.println("F2:"+B.andThen(A).apply(5));
}
/** F1:36 */
/** F1:36 */
/** F2:26 */
/** F2:26 */
compose等价于B.apply(A.apply(5)),而andThen等价于A.apply(B.apply(5))。
Consumer 接口
Consumer 接口翻译过来就是消费者,顾名思义,该接口对应的方法类型为接收一个参数,没有返回值,可以通俗的理解成将这个参数’消费掉了’,一般来说使用Consumer接口往往伴随着一些期望状态的改变或者事件的发生,例如最典型的forEach就是使用的Consumer接口,虽然没有任何的返回值,但是却向控制台输出了语句。
public static void main(String[] args) {
Consumer<String> printString = s -> System.out.println(s);
printString.accept("helloWorld!");
//控制台输出 helloWorld!
}
Supplier 接口
Supplier 接口翻译过来就是提供者,和上面的消费者相反,该接口对应的方法类型为不接受参数,但是提供一个返回值,通俗的理解为这种接口是无私的奉献者,不仅不要参数,还返回一个值,使用get()方法获得这个返回值
Supplier<String> getInstance = () -> "HelloWorld!";
System.out.println(getInstance.get());
// 控偶值台输出 HelloWorld
Predicate 接口
predicate<T,Boolean> 谓语接口,顾名思义,中文中的‘是’与‘不是’是中文语法的谓语,同样的该接口对应的方法为接收一个参数,返回一个Boolean类型值,多用于判断与过滤,当然你可以把他理解成特殊的Funcation<T,R>,但是为了便于区分语义,还是单独的划了一个接口,使用test()方法执行这段行为
public static void main(String[] args) {
Predicate<Integer> predOdd = integer -> integer % 2 == 1;
System.out.println(predOdd.test(5));
//控制台输出 5
}
其他的接口
介绍完正面这四种最基本的接口,剩余的接口就可以很容易的理解了,java8中定义了几十种的函数接口,但是剩下的接口都是上面这几种接口的变种,大多为限制参数类型,数量,下面举几个例子。
类型限制接口
参数类型,例如IntPredicate
,LongPredicate
, DoublePredicate
,这几个接口,都是在基于Predicate
接口的,不同的就是他们的泛型类型分别变成了Integer,Long,Double
。
IntConsumer
,LongConsumer
, DoubleConsumer
比如这几个,对应的就是Consumer<Integer>,Consumer<Long>,Consumer<Double>
数量限制接口
有些接口需要接受两名参数,此类接口的所有名字前面都是附加上Bi,是Binary的缩写,开头也介绍过这个单词了,是二元的意思,例如BiPredicate
,BiFcuntion
等等,而由于java没有多返回值的设定,所以Bi指的都是参数为两个。
计算 a + b的BiFcuntion如果限制条件为Integer的话 往往要这么写BiFunction<Integer,Integer,Integer>
前2个泛型代表参数,最后一个代表返回值