Java特性:Function接口的使用
摘要
Java8 添加了一个新的特性 Function,顾名思义这一定是一个函数式的操作。我们知道 Java8 的最大特性就是函数式接口。所有标注了 @FunctionalInterface
注解的接口都是函数式接口,具体来说,所有标注了该注解的接口都将能用在 Lambda 表达式上。
Function 接口的源码
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
/**
* @return a composed function that first applies the {@code before}
* function and then applies this function
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before){Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* @return a composed function that first applies this function and then
* applies the {@code after} function
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after){Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
}
源码解析
1.apply
Function 是一个泛型类,其中定义了两个泛型参数 T 和 R,在 Function 中,T 代表输入参数,R 代表返回的结果。Function 就是一个函数,其作用类似于数学中函数的定义 ,(x,y)跟 <T,R> 的作用几乎一致。
$$
y = f(x)
$$
所以 Function 中没有具体的操作,具体的操作需要我们去为它指定,因此 apply 具体返回的结果取决于传入的 Lambda 表达式。
R apply(T t);
举个例子:
public void test(){
Function<String,Object> test=i->i+1;
test.apply(5);}
/** print:6*/
Lambda 表达式定义了一个行为使得 i 自增 1,我们使用参数 5 执行 apply,即对参数 5 执行 Lambda 表达式,最后返回 6。
函数式编程之前我们定义一组操作首先想到的是定义一个方法,然后指定传入参数,返回我们需要的结果。函数式编程的思想是先不去考虑具体的行为,而是先去考虑参数,具体的方法我们可以后续再设置。
再举个例子:
public void test(){
Function<Integer,Integer> funOne = i -> i + 1;
Function<Integer,Integer> funTwo = i -> i * i;
System.out.println(cal(funOne, 5));
System.out.println(cal(funTwo, 3));}
public static Integer calcute(Function<Integer, Integer> funs, Integer number){
return funs.apply(number);
}
/**
*print:6
*print:9
*/
通过传入不同的 Function,实现了在同一个方法中实现不同的操作。在实际开发中这样可以大大减少很多重复的代码,比如我在实际项目中有个新增用户的功能,但是用户分为 VIP 和普通用户,且有两种不同的新增逻辑。那么此时我们就可以先写两种不同的逻辑。除此之外,这样还让逻辑与数据分离开来,我们可以实现逻辑的复用。
实际开发中的逻辑可能很复杂,比如两个方法 F1,F2 都需要两个个逻辑 AB,但是 F1 需要 A -> B,F2 方法需要 B -> A。这样的我们用刚才的方法也可以实现,代码如下:
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("F2:"+A.apply(B.apply(5)));}
/** F1:36 */
/** F2:26 */
也很简单呢,但是这还不够复杂,假如我们 F1,F2 需要四个逻辑 ABCD,那我们还这样写就会变得很麻烦了。
2. compose 和 andThen
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。
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after){Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
andThen 跟 compose 正相反,先执行当前的逻辑,再执行传入的逻辑。
这样说可能不够直观,我可以换个说法给你看看。
compose 等价于 B.apply(A.apply(5)),而 andThen 等价于 A.apply(B.apply(5))。
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 */
我们可以看到上述两个方法的返回值都是一个 Function,这样我们就可以使用建造者模式的操作来使用。
B.compose(A).compose(A).andThen(A).apply(5);