首页 > 编程语言 >Java函数式编程:二、高阶函数,闭包,函数组合以及柯里化

Java函数式编程:二、高阶函数,闭包,函数组合以及柯里化

时间:2022-11-03 00:01:21浏览次数:73  
标签:闭包 Function Java String int return public 函数

承接上文:Java函数式编程:一、函数式接口,lambda表达式和方法引用
这次来聊聊函数式编程中其他的几个比较重要的概念和技术,从而使得我们能更深刻的掌握Java中的函数式编程。
本篇博客主要聊聊以下几个问题

  • 高阶函数
  • 闭包概念
  • 函数组合处理函数的使用
  • 柯里化以及部分求值
    下面开始:

1、高阶函数

高阶函数这里指的可不是数学里的那个,这里主要要从维度这个概念理解,本来函数生成的是值,也就是说,函数比值高维,那么如果我们有一个函数能生成函数或者是以函数为参数,那么显然就比普通的生成值的函数更高维,因为我可以生成你。

定义:高阶函数是一个能接受函数作为参数能够把函数作为返回值的函数。

public interface Function{
    String str(String s);
}

public class Procudure{
    // 下面就是一个标准的高阶函数
    public Function(String s){
        return s -> s.upperCase();
    }
}

这里有两点:

  • 我们可以通过继承java.util.function中的接口,或是自定义一个函数式接口来为专门的接口创建别名
  • 有了lambda表达式,很明显,我们很轻松就能创建并返回一个函数

但是这只是基本的,还记得函数式编程的意义吗?这里的关键在于,有时候,我们可以根据接受的函数,让高阶函数生成一个新的函数。

public class test {
    public static Function<String, String> transform(Function<String, String> f){
        return f.andThen(String::toUpperCase);
    }

    public static void main(String[] args) {
        Function<String, String> transform = test2.transform(str -> {
            return str.substring(0, 2);
        });

        String s = transform.apply("abcdefg");
        System.out.println(s);
    }
}

可以看到,这里我们通过andThen()这个Function接口的通用方法,连接了前后两个方法,并且使得无论我们输入了什么,都会将该字符串转化为全大写,后面我们输入了一个截取前两个字符作为返回值的方法,但很明显,这里可以有更多的选择,并且我们实际上也可以通过方法引用来引用某些定义好的函数,非常灵活。


2、闭包

什么是闭包?

考虑一个lambda表达式,它使用了其函数作用域之外的变量。当返回该函数时会发生什么呢?也即,当我们通过调用lambda表达式产生的匿名方法引用这些外部变量会发生什么呢?

如果一门语言能够解决这个问题,我们就认为该语言是支持闭包的,或者也可以说它支持词法作用域。

这里还涉及到一个术语:变量捕获

上面听起来是不是不明白,没关系,给个例子:

public class Example{
    IntSupplier plus(int x){
        int y = 1;
        return () -> x + y;
    }
}

考虑这个类和其中的方法plus(int x),你会不会发现有一些问题。

因为我们的plus(int x)方法返回的是一个函数,这里假设返回的函数是f(int x),也就是说,f(int x)返回时,plus(int x)已经执行结束,所以其中的变量int y = 1;已经脱离了作用域,那么等到我们获取了f(int x)的对象再调用到f(int x)方法时,这个y要怎么办呢?

你会发现,上面的这个方法是可以被编译执行成功的,但是下面的这个就不行:

public class Example{
    IntSupplier plus(int x){
        int y = 1;
        return () -> x + (++y);
    }
}

为什么呢?编译器提示:lambda 表达式中使用的变量应为 final 或有效 final

这句话就说的很明白了,对于第一个例子,我们的y虽然没final关键字,但它是事实上的final变量,一旦这里赋值就不会再改动,而对于第二个方法来说则相当于把y赋予了新的值。

这里如果我们使用的是引用,比如下面这个例子

public class Example{
    IntSupplier plus(int x){
        Queue<Integer> y = new LinkedList<>();
        y.offer(1);
        return () -> x + y.poll();
    }
}

注意,这里是可以通过编译的,因为实际上我们只需要保证这个引用所指向的对象不被修改,避免后面调用返回的函数时却突然发现找不到对应的对象即可。

所以,Java提供的闭包的条件是,我们必须要能够保证,被捕获的变量是final

不过要注意的是,这里如果是多线程情况的话,不能保证线程安全。


3、函数组合

之前我们有提到andThen()这个方法,这些方法在Java.util.function包中的各个函数式接口中各有提供,总的来说有这么几种:

  • andThen(arg)
    • 先执行原始操作,再执行参数操作
  • compose(arg)
    • 先执行参数操作,再执行原始操作
  • and(arg)
    • 对原始谓词和参数谓词做逻辑与运算
  • or(arg)
    • 对原始谓词和参数谓词做逻辑或运算
  • negate()
    • 所得谓词为原始谓词取反

在后面的流处理章节,你将会体会到这些函数组合的力量。


4、柯里化和部分求值

所谓柯里化,就是指将一个接受多个参数的函数转变为一系列,只接受一个参数的函数,在面向函数编程里这么做的目的,就跟我们在面向对象编程里需要抽象出接口和抽象类是一样的,目的就是我们可以通过部分求值来复用这些代码。

public class Currying{
    // 未柯里化的函数
    static String unCurried(String a,String b){
        return a + b;
    }
    
    // 柯里化的函数
    static Function<String, Function<String, String>> Curried(){
        return a -> b -> a + b;
    }
    
    // 实例
    public static void main(String[] args) {
        Function<String, Function<String, String>> curried = test2.Curried();

        System.out.println(unCurried("hello ", "World"));

        Function<String, String> firstWord = curried.apply("hello ");

        System.out.println(firstWord.apply("World"));
        System.out.println(firstWord.apply("My friend"));
        System.out.println(firstWord.apply("My love"));
    }
    
    /** 
    	输出
        hello World
        hello World
        hello My friend
        hello My love
    **/
}

简单来说就是每一层都返回下一层的函数,直到最终返回我们需要的值为止。

标签:闭包,Function,Java,String,int,return,public,函数
From: https://www.cnblogs.com/hgly1999/p/16852995.html

相关文章

  • java生成pdf
    参考链接https://blog.csdn.net/weixin_39806100/article/details/86616041https://www.cnblogs.com/next-yrz/p/10134670.htmlhttps://www.likecs.com/show-306660785.......
  • java----GUI编程
    1packagecom.cilinmengye.HouseWork5;23importjavax.swing.*;4importjava.awt.*;5importjava.awt.event.ActionEvent;6importjava.awt.event.Actio......
  • java中string类的使用
    packagecom.te.jdkapi;/*字符串不可变。一经初始化就不会改变*/publicclassStudy_String{publicstaticvoidmain(String[]args){Stringa="1......
  • Java学习——10.02
     不得不说,Java真的很好上手,只不过搭建环境真的难。今天的话搞了一个喜欢的Java开发背景图,看着就心情愉悦。再加上今天idea学生认证下来,就很棒!    废话不多说进入正......
  • javascript - 练习题(若干)
    慢慢收集一些习题、考题练习1问:X,Y,Z分别是多少?varx=1,y=z=0;functionadd(n){returnn=n+1;}y=add(x);functionadd(n){returnn=n+3;}z=add(x);conso......
  • Java GUI编程(未完待续...)
    JavaGUIAWT1.组件和容器Frame弹出一个窗口packageuichuan;importjava.awt.*;importjava.awt.event.WindowAdapter;importjava.awt.event.WindowEvent;pu......
  • java中的equals工具包和hashcode
    packagecom.te.jdkapi;importcom.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;importjava.util.Objects;/*学习equals的方法*/publicclassStudy_Equels......
  • JAVA-猜数字游戏功能实现事务机制
    packagecom.itheima;importjavax.swing.*;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;importjava.util.Random;publicclassCAI......
  • 2022 JAVA-static this 关键字
         ......
  • Java IO流
    创建文件的三个方法方法一 方法二 方法三    查询文件删除文件 删除目录 创建文件 scanPrintTest scanTest ......