欢迎浏览我的博客 获取更多精彩文章
Java函数式编程(一)–Function的使用
在函数式编程中,我们用的最多的往往是Function接口.通常来说,我们很少会直接使用这个接口,但是在Java的函数式编程中,许多组件都会与这个接口有关.需要注意的是,很多人会混淆Java8中新增的Stream API与函数式编程的概念,事实上,Stream API是一种为了实现自动化并行的惰性求值的解决方法,与函数式没有太大关系,但是其与函数式编程结合会很好用.
回到正题,Function是函数式编程的基石之一,其与函数柯里化,高阶函数,复合函数等等概念的实现有关.我们现在看看其接口的组成
Function Interface
单一函数声明
public interface Function<T,R>{
R apply(T t);
}
为了说明Function的核心概念,我们省略了两个实现复合函数的方法(compose和andthen,稍后会说到)
毫无疑问,这是一个函数式的接口,所以完全可以使用lambda表达式来实现这个接口,我们来看一下一些简单的应用
Function<Integer,Integer> triangle = arg -> arg*3;
Function<Integer,Integer> square = arg -> arg*arg;
int result1 = triangle.apply(1); //result1: 3
int result2 = square.apply(1);//result2: 1
复合函数声明
当然,我们不只是希望能够使用单一的函数,我们想要使用复合函数来达到更多的目的.复合函数可以理解成是基本Function接口的二元操作,我们可以用一个方法,来达到这个效果,那就是compose方法(andthen函数与compose其实是一对互为冗余的方法,他们实现的目的是一样的,只是角度不一样,所以在这里只说一下compose)
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
这个是在Function接口中实现的默认方法compose,对于单个参数的情况,与我们在数学公式中常见的
g(x) = x * 2,f(x) = x+x, f(g(x)) = x * 2+x * 2的效果是一样的,所以我们来看一个简单的使用
Function<Integer,Integer> area1 = triangle.compose(square);
Function<Integer,Integer> area2 = square.compose(triangle);
area1.apply(1);//tri(squ(1))
area2.apply(1);//squ(tri(1))
多参函数声明
在Function接口中,我们只有一个输入参数和一个输出,那么,如果要定义一个多参的函数,要怎么做呢?
我们可以从两个角度来看,第一个角度,我们可以将输入参数看作是一个元组,如果有两个输入参数,那么输入就是一个二元组,以此类推.第二个角度,我们可以一个接着一个地应用不同的参数,除了最后一个参数外,每一个参数应用后都会返回一个新的函数.
我们称第二个方法为函数的柯里化,这是一个高级函数的特性,我们来看一下使用的方法
Function<Integer, Function<Integer, Integer>> add = x -> (y -> (x + y));
add.apply(3).apply(5);//result: 8
在第一行,我们定义了一个输入为(一个输入为一个整数,输出为一个整数的Function),输出为一个整数的Function.并在第二行用apply方法进行传参.那么,如果一个方法有3个参数,又要怎么写呢
Function<Integer, Function<Integer, Function<Integer, Integer>>> axPlusb = a -> (x -> (b -> (a * x + b)));
int y = axPlusb.apply(2).apply(3).apply(4);
我们定义了一个y=ax+b的函数.
但是问题也接踵而至,在代码中,我们发现这样写太过于繁杂了,并且很长.不过幸好,在柯里化的函数中,通常只有两个值到四个值左右,所以我们可以定义各自的接口.
interface BiaryOperator extends Function<Integer,Function<Integer,Integer>>{
}
//
BiaryOperator add = x -> (y -> (x + y));
高阶函数声明
我们在复合函数中,使用了一个compose方法来接受两个函数并返回一个复合了之后的函数.那么,如果我们可以用一个函数,做到同样的事情,我们就称这个函数为高阶函数
首先,我们要确定参数,他的输入是两个函数,输出是一个函数.根据上面所说的柯里化,我们可以分别对其进行类似多参函数的声明.有如下声明
Function<Function<Integer, Integer>, Function<Function<Integer, Integer>, Function<Integer, Integer>>> compose = x -> y -> z -> (x.apply(y.apply(z)));
可以在一行内写完.
自动柯里化
自动柯里化在函数式编程中是一个很重要的概念,自动柯里化与部分应用函数紧密地结合着.柯里化包括了把接受元组的函数替换为可以部分应用各个参数的函数.我们在使用完全应用函数前,需要对元函数进行柯里化来将其转化为部分应用函数.例如,一个接受三个参数的函数可以被柯里化成一个生成单参函数的二元函数.
//假设我们有一个双参的柯里化函数,我们需要接受它的第一个参数来将其转化为部分应用函数
<A,B,C> Function<B,C> partialA(A a, Function<A,Function<B,C>> f){
return f.apply(a);
}
//再假设我们有一个双参的柯里化函数,我们需要接受它的第二个参数来将其转化为部分应用函数
<A,B,C> Function<A,C> partialB(B b,Function<A,Function<B,C>> f){
return a->f.apply(a).apply(b);
}
下面我们来看一些柯里化应用的例子.其实,我们要实现柯里化,最重要的一点,就是要跟着类型走,只要定义好了方法的签名,那么实现柯里化就是一件很简单的事情了
首先来看一下将一个多参函数柯里化的例子
//将以下函数转换成一个柯里化函数
<A,B,C,D> String func(A a,B b,C c,D d){
return String.format("%s %s %s %s",a,b,c,d);
}
//在转换前,我们只需要知道多层输入的类型和输出,那么就可以简单地写出方法的签名了
<A,B,C,D> Function<A,Function<B,Function<C,Function<D,String>>>> func()
//然后就是实现了,实现起来十分简单,就是单输入柯里化的嵌套
{
return a->b->c->d-> String.format("%s %s %s %s",a,b,c,d);
}
//应用也很简单
String string = func().apply("A").apply("B").apply("C").apply("D");
再来看一个交换部分应用参数的例子.在应用函数式的过程中,我们可能需要交换apply的顺序,这个时候就要牵涉到交换柯里化顺序了
public static <T,U,V> Function(U,Function(T,V)) reverseArgs(Function<T,Function<U,V>> f){
return u->t->f.apply(t).apply(u);
}
//仍然是那句话,我们需要跟着参数走,函数f apply的顺序是不会变的,我们要改变的是柯里化传值的顺序
完整的Function接口函数
综合上面所说的,我们可以写出一个完整的,包含复合函数生成的Function接口
import java.util.Objects;
/** * @author Boyn * @date 2019/8/14 * @description 这个类是函数式编程的基础,实现这个接口可以表示一个单输入单输出的函数 * 这个接口还包含了许多复合函数与柯里化的实现 */
/*频繁往外读取内容的,适合用上界Extends。 经常往里插入的,适合用下界Super。*/
@FunctionalInterface
public interface Function<T,U> {
U apply(T arg);
/** * 生成复合函数 */
default <R> Function<R,U> compose(Function<? super R,? extends T> before){
Objects.requireNonNull(before);
return (R r)-> apply(before.apply(r));
}
/** * 生成复合函数的另一种表达 */
default <R> Function<T,R> andThen(Function<? super U,? extends R> after){
Objects.requireNonNull(after);
return (T t) -> after.apply(this.apply(t));
}
/** * 实现一个恒等函数 */
static <T> Function<T,T> identity(){
return t->t;
}
/** * 生成复合函数的静态方法 */
static <T,U,V> Function<V,U> compose(Function<T,U> f,Function<V,T> g){
return (V v)->f.apply(g.apply(v));
}
/** * 生成复合函数的静态方法 */
static <T,U,V> Function<T,V> andThen(Function<T,U> f,Function<U,V> g){
return (T t)->g.apply(f.apply(t));
}
}
今天的文章Java函数式编程(一)–Function的使用分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/32118.html