java函數式編程--柯里化(Currying),閉包


近年來函數式編程趨熱,在積累了一定的經驗后,我也嘗試着用函數式編程的思想來重新理解java編程。
閉包
閉包在Js中作為Js的入門概念,指的是函數的執行環境依賴於創建時的一系列作用域鏈的現象。
var v="a";
var fn=(function(){
        v="b";
     return  function(){
          v="c";
          console.log(v);
     }     
})();
fn();
當我們分別注釋v的“c”,“b”的賦值時,v的值將會向外尋找,對應的值也是“c”,“b”,“a”
也就是說fn帶走了當時的數值作用域。
由於閉包的這一特性,我們得以訪問函數內部變量。
而在Java中,我們也可以找到類似的功能:
public class Closure {
    public  String version=""; 

    public static void main(String[] args) {
        Closure c1=new Closure();
        c1.version="1.0";
        Moment m1=c1.getMoment();
        System.out.println(m1.getVersion());
        
        c1.version="2.0";
        Moment m2=c1.getMoment();
        System.out.println(m2.getVersion());
    }

    public Moment getMoment(){
        return new Moment() {
            
            @Override
            public String getVersion() {
                
                return version;
            }
        };
    }

}

interface Moment{
    String getVersion();
}

 

以上分別輸出1.0     2.0,說明m1,m2記住了當時的狀態,也就是說每次聲明Moment對象的時候,編譯器會把相關的值拷貝副本,放到對象的私有變量里。
利用這個特性,我們可以實現一些功能:比如一個創建成本很高的對象,需要輸出一些包涵自己屬性的一些特定對象,這時可以通過閉包這種方式方便的創建所多特定對象,而且每個特定對象可以保持自己的一些獨立屬性。
 
柯里化
在使用JavaScript的時候,有時會使用科里化,百度百科的釋義:
在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受余下的參數且返回結果的新函數的技術。這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的。
不同於Functor,我認為科里化更側重於函數的轉換和組合。我們可以使用科里化的概念包裝一些函數
var calc= function(num){
    return function(y){
        return num + y;
    }
}
var c1= calc (1);
var c2= calc (-1);
其中c1和c2其實是兩個不同的函數,通過這種方式我們合理的組合函數
calc(10)(1) 11
calc(0.1)(1) 1.1
而JavaScript動態類型的特點又是得我們可以進行更高級的變換,如:
函數組合
var calc= function(numHandler){
    return function(y){
        return numHandler( y)+1;
    }
}
特定的組合可以簡化步驟:
function square(){}
function add(){}
function dvid(){}
function map(handler,list){
}
map(add,list);
map(dvid,list);
map(square,list);
柯里化之后
 
var h=handler(add,dvid,square)
map(h,list)
 
還可以延遲計算:
var curry = function(fn) {
    var _args = []
    return function cb() {
        if (arguments.length == 0) {
            return fn.apply(this, _args)
        }
        Array.prototype.push.apply(_args, arguments);
        return cb;
    }
}
var sum=function(){
     var s=0;
     for (var i = 0;i<arguments.length;i++) {
          s += arguments[i];
     }
     return s;
}
var sumc=curry(sum);
sumc(1)(2)(3)();

java中缺少動態類型,又缺少類似於Prototype的原型,我們只能通過一些其他特性來彌補。
JDK7種我會使用接口和匿名函數來實現動態類和方法,JDK8當然是使用Function接口。先來說JDK7.
JDK7 java實現函數組合。
public class keli {

    public  calculator changeCalc(final initialize init, final int num) {
        return new calculator() {

            @Override
            public int cal(int addend) {
                return init.init(num) + addend;

            }
        };
    }

    public static void main(String[] args) {
        
        initialize init1 = new initialize() {

            @Override
            public int init(int num) {
                num = num * 10;
                return num;
            }
        };
        
        initialize init2 = new init2();
        
        calculator clac1=new keli().changeCalc(init1,3);
        calculator clac2=new keli().changeCalc(init2,3);
        System.out.println(clac1.cal(2));
        System.out.println(clac2.cal(2));
        
    }
}

class init2 implements initialize{

    @Override
    public int init(int num) {
        num = num / 10;
        return num;
    }    
}

interface calculator {
    int cal(int num);
}

interface initialize {
    int init(int num);
}
注意:在JDK7以下需要加final關鍵字來固定住函數參數(JDK8不用加,編譯器內部幫我們加了)。
上面我們通過將匿名函數作為參數傳入構造了兩個新的函數clac1
和 clac2,他們分別擁有不同的作用域。在實際中一般明確的業務不會這樣寫,但是處理動態的流程,比如自定義的計算公式,一些插件會用到。
 
JDK8中的默認方法
jdk8實現了默認方法,簡而言之就是在街口中添加default關鍵字,給繼承類添加方法,這其實是一個妥協的方案,為了給原有類型增加功能,又不破壞大的結構。類似方式有在C#中我們看到的擴展方法。
今天我們借默認方法實現一些類似Prototype的功能

 

public class currying3 implements Proto {
    public List<String> paramList = null;

    public currying3(List<String> paramList) {
        this.paramList = paramList;
    }

    @Override
    public List<String> getList() {
        
        return this.paramList;
    }
    
    public static void main(String[] args) {
        
        List<String> paramList = new ArrayList<String>();
        Proto p = new currying3(paramList);
     //延遲計算結果,當參數為空時計算 p.handle(
"aa").handle("bb").handle("cc").handle(); System.out.println("end....."); List<String> paramList1 = new ArrayList<String>(); Proto p1 = new currying3(paramList1); p1.handle("dd").handle("ee").handle("ff").handle(); System.out.println("end....."); } } interface Proto { public List<String> getList(); default Proto handle(String... param) { List<String> paramList=this.getList(); if (param == null || param.length <= 0) { for (String Str : paramList) { System.out.println(Str); } return null; } else { for (String Str : param) { paramList.add(Str); } return this; } } } 
輸出結果:
aa
bb
cc
end.....
dd
ee
ff
end.....
類似於鏈式編程的寫法,但是延遲執行。當handle參數為空的時候,計算結果,這種方式可以用在可變數量的統計,延遲計算,查詢中(不同於Hibernate的延遲查詢,Hibernate通過代理類,聲明時給予空屬性,真正屬性求值時在調用查詢方法)。
 
函數式接口
在動態語言中,我可以使用一個var來表示function,但是java作為動態語言不允許這樣做,只能使用接口作為參數,然后笨拙的使用接口中的方法。早先C#中實現了委托作為方法指針,現在java8中終於實現了類似的功能-函數式接口
為什么要用函數式接口?
函數式接口可以讓程序更好的組織和被理解。
(圖來自阮一峰  http://www.ruanyifeng.com/blog/2015/07/monad.html,表示Functor的概念)
我用這幅圖來描述,對指定類型,進行指定的運算,這一步驟
它將函數作為參數傳遞,處理指定的值,它也可以用作科里化來組合函數
如一個這樣的方法
static int handlerNum(Function<Integer,Integer> function , Function<Integer,Integer> function2 , int num ){
               int   result = function .apply( num );
                    result = function2 .apply( result );
               return result ;
       }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM