Java8之Function接口

Java8 添加了一个新的特性Function,是一个函数式接口

所有标注了@FunctionalInterface注解的接口都是函数式接口,具体来说,所有标注了该注解的接口都将能用在lambda表达式上。
先看源码

@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));
    }
    /**
     * Returns a function that always returns its input argument.
     *
     * @param <T> the type of the input and output objects to the function
     * @return a function that always returns its input argument
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

实例化
对于这样的函数式接口,因为只有一个抽象函数,所以只要实现了该函数就能够实例化对象了。所以可以通过下面这一行代码的方式实例化一个“接口”对象:
例如:

Function<Integer, Integer> fun = x -> x + 1;

其中等号(=)右边的是Function接口中的那个抽象方法的实现。鉴于此,我们可以得出一个结论:
对于函数式接口,形如这样的表达式(x -> x + 1)能够实例化一个对象。并且该表达式正是那个唯一的抽象方法的实现。

源码解析
1.apply
讲完了上面这些就可以开始研究源码了。
首先我们已经知道了Function是一个泛型类,其中定义了两个泛型参数T和R,在Function中,T代表输入参数,R代表返回的结果。也许你很好奇,为什么跟别的java源码不一样,Function 的源码中并没有具体的逻辑呢?
其实这很容易理解,Function 就是一个函数,其作用类似于数学中函数的定义 ,
(x,y)跟<T,R>的作用几乎一致。

y=f(x)

所以Function中没有具体的操作,具体的操作需要我们去为它指定,因此apply具体返回的结果取决于传入的lambda表达式。

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*/

我们通过传入不同的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,那我们还这样写就会变得很麻烦了。
可以最终理解成 R apply(T t) -> 将function对象应用到输入的参数上,并且输出结果
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正相反,先执行当前的逻辑,再执行传入的逻辑。

这样说可能不够直观,我可以换个说法给你看看

B.compose(A).apply(5)
compose等价于B.apply(A.apply(5)),
B.andThen(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).andThen(A).apply(5);
从后向前看,先看 andThen(A) 先执行A, i=i+1 = 6, 然后再看 B.compose(A) 先执行 B
i=iI =36 然后执行 A i=i+1=37
B.andThen(A).compose(A).apply(5)
从后向前看,先看compose(A) 先执行 B.compose(A) ,然后B.compose(A) 中先执行A
i=i+1=6 再执行B i=i
i=36, 再执行compose(A) 执行 A i =i+1 = 37
3 identity方法
Java 8允许在接口中加入具体方法。接口中的具体方法有两种,default方法和static方法,identity()就是Function接口的一个静态方法。
Function.identity()返回一个输出跟输入一样的Lambda表达式对象,等价于形如t -> t形式的Lambda表达式

static <T> Function<T, T> identity() {
        return t -> t;
    }

参考资料:
探索Java8:(二)Function接口的使用
JDK8之Function接口

  • 10
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鹏小站

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值