成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java 8怎么了:局部套用vs閉包

3fuyu / 2931人閱讀

摘要:本文主要介紹了中的閉包與局部套用功能,由國(guó)內(nèi)管理平臺(tái)編譯呈現(xiàn)。譬如,認(rèn)為給帶來了閉包特性就是其中之一。但是首先,我們將考慮如何利用閉包進(jìn)行實(shí)現(xiàn)。很顯然,閉包打破了這一準(zhǔn)則。這就是局部調(diào)用,它總是比閉包更為穩(wěn)妥。

【編者按】本文作者為專注于自然語(yǔ)言處理多年的 Pierre-Yves Saumont,Pierre-Yves 著有30多本主講 Java 軟件開發(fā)的書籍,自2008開始供職于 Alcatel-Lucent 公司,擔(dān)任軟件研發(fā)工程師。

本文主要介紹了 Java 8 中的閉包與局部套用功能,由國(guó)內(nèi) ITOM 管理平臺(tái) OneAPM 編譯呈現(xiàn)。

關(guān)于Java 8,存在著許多錯(cuò)誤觀念。譬如,認(rèn)為Java 8給Java帶來了閉包特性就是其中之一。這個(gè)想法是錯(cuò)的,因?yàn)殚]包特性從Java誕生之初就已經(jīng)存在了。然而閉包是有缺陷的。盡管Java 8似乎傾向于函數(shù)式編程,我們?nèi)詰?yīng)盡力避免使用Java閉包。但是,Java 8并沒有在此方面提供過多幫助。

我們知道,參數(shù)求值時(shí)間是使用方法和使用函數(shù)時(shí)的一個(gè)重大區(qū)別。在Java中,我們可以寫一個(gè)帶參數(shù)且有返回值的方法。但是,這可以被稱作函數(shù)嗎?當(dāng)然不能。方法只可以通過調(diào)用進(jìn)行操縱,這表示它的參數(shù)會(huì)在該方法執(zhí)行前取值。這是Java中參數(shù)按值傳遞的結(jié)果。

函數(shù)則與之不同。操作函數(shù)時(shí)我們可以不計(jì)算參數(shù),且對(duì)參數(shù)何時(shí)取值有絕對(duì)的控制權(quán)。而且,如果一個(gè)函數(shù)有多個(gè)參數(shù),它們可以不同時(shí)取值。這一點(diǎn)通過局部套用就可以做到。但是首先,我們將考慮如何利用閉包進(jìn)行實(shí)現(xiàn)。

閉包舉例

對(duì)函數(shù)而言,閉包能夠在封裝的上下文中獲取內(nèi)容。在函數(shù)式編程中,一個(gè)函數(shù)的結(jié)果應(yīng)當(dāng)僅由其參數(shù)決定。很顯然,閉包打破了這一準(zhǔn)則。

請(qǐng)看Java 5/6/7中的示例:

private Integer b = 2; 
    List list = Arrays.asList(1, 2, 3, 4, 5); 
    System.out.println(calculate(list.stream(), 3).collect(toList())); 
    private Stream calculate(Stream stream, Integer a) { 
      return stream.map(new Function() { 
        @Override 
        public Integer apply(Integer t) { 
          return t * a + b; 
        } 
      }); 
    } 
    public interface Function { 
      U apply(T t); 
    }

以上代碼將產(chǎn)生如下結(jié)果:

[5, 8, 11, 14, 17]

所得結(jié)果是函數(shù) f(x) = x 3 + 2 對(duì)于列 [1, 2, 3, 4, 5]的映射。到這一步都沒什么問題。但是3和2可以用其他值替換嗎?換句話說,它難道不是函數(shù)f(x, a, b) = x a + b 對(duì)于該列的映射嗎?

是,也不是。不是的原因在于a和b都被隱性定義了final關(guān)鍵詞,因此它們?cè)诤瘮?shù)取值時(shí)作為常數(shù)參與計(jì)算。但是當(dāng)然,它們的值也會(huì)有變動(dòng)。它們的final屬性(在Java 8中隱性定義,在之前版本中則顯性定義)只是編譯器優(yōu)化編譯過程的一種方式。編譯器并不在乎任何潛在的變動(dòng)值。它只在乎引用有沒有發(fā)生變動(dòng),也就是說,它想要確保Integer整數(shù)對(duì)象ab的引用不發(fā)生變化,但并不在意它們的取值。這個(gè)特性在以下代碼中可以看出:

 private Integer b = 2; 
    private Integer getB() { 
      return this.b; 
    } 
    List list = Arrays.asList(1, 2, 3, 4, 5); 
    System.out.println(calculator.calculate(list.stream(), new Int(3)).collect(toList())); 
    private Streamcalculate00(Streamstream, final Int a) { 
      return stream.map(new Function() { 
        @Override 
        public Integer apply(Integer t) { 
          return t * a.value + getB(); 
        } 
      }); 
 
    } 

private class Int {

     public int value; 
     public Int(int value) { 
       this.value = value; 
     } 
    }

在這里,我們使用了可變對(duì)象a(屬于Int類,而不是不可變的Integer類),以及一個(gè)方法來獲取b?,F(xiàn)在,我們來模擬一個(gè)有三個(gè)變量的函數(shù),但是仍舊使用僅有一個(gè)變量的函數(shù),同時(shí)使用閉包來代替其他兩個(gè)變量。很顯然,這是非函數(shù)性的,因?yàn)樗蚱屏藘H依賴于函數(shù)參數(shù)的準(zhǔn)則。

結(jié)果之一是,盡管有需要,我們也不能在別的地方重用這個(gè)函數(shù),因?yàn)樗蕾囉谏舷挛亩粌H僅依賴于參數(shù)。我們要復(fù)制這些代碼才能實(shí)現(xiàn)重用。另一個(gè)結(jié)果是,由于它需要上下文才能運(yùn)行,我們也不能多帶帶進(jìn)行函數(shù)測(cè)試。

那么,我們應(yīng)該使用帶有三個(gè)參數(shù)的函數(shù)嗎?我們可能會(huì)認(rèn)為,這不可能實(shí)現(xiàn)。因?yàn)榫唧w的實(shí)現(xiàn)過程與三個(gè)參數(shù)何時(shí)取值相關(guān)。它們都在不同的地方取值。如果我們剛才使用的是帶有三個(gè)參數(shù)的函數(shù),它們就必須同時(shí)取值。而映射方法只會(huì)映射帶一個(gè)參數(shù)的函數(shù)到流,不可能映射帶有三個(gè)參數(shù)的函數(shù)。因此,其余兩個(gè)參數(shù)在函數(shù)綁定時(shí)(也即傳遞給映射時(shí))必須已經(jīng)取值。解決方法是先對(duì)其余兩個(gè)參數(shù)取值。

我們也可以用閉包來實(shí)現(xiàn)這一功能,但是所得代碼是不可測(cè)試的,且可能存在重疊。

使用Java 8 的句法(lambdas)也無(wú)法改變這一狀況:

private Integer b = 2; 
    private Stream calculate(Stream stream, Integer a) { 
      return stream.map(t -> t * a + b); 
 
    }

我們需要的是一種在不同時(shí)間獲取三個(gè)參數(shù)的方法——Currying(局部套用,也稱柯里化函數(shù),盡管它其實(shí)是Moses Sh?nfinkel發(fā)明的)。

使用局部閉包

局部閉包就是逐一對(duì)函數(shù)參數(shù)取值,每一步都生成少一個(gè)參數(shù)的新函數(shù)。舉例來看,如果我們有如下函數(shù):

f(x, y, z) = x * y + z

我們可以同時(shí)取參數(shù)值為2,4,5,得到以下方程:

f(3, 4, 5) = 3 * 4 + 5 = 17

我們也可以只取一個(gè)參數(shù)為3,得到以下方程:

f(3, y, z) = g(y, z) = 3 * y + z

現(xiàn)在,我們得到了只有兩個(gè)參數(shù)的新函數(shù)g。再對(duì)該函數(shù)進(jìn)行局部套用,將4賦值給y:

g(4, z) = h(z) = 3 * 4 + z

給參數(shù)賦值的順序?qū)τ?jì)算結(jié)果并無(wú)影響。此處,我們并不是在局部相加,(如果是局部相加,我們還得考慮運(yùn)算符優(yōu)先級(jí)。)而是在進(jìn)行對(duì)函數(shù)的局部應(yīng)用。

那么,我們?nèi)绾卧贘ava中實(shí)現(xiàn)這種方法呢?以下是在Java5/6/7中的應(yīng)用:

private static List calculate(List list, Integer a) { 
      return list.map(new Function>>() { 
        @Override 
        public Function> apply(final Integer x) { 
          return new Function>() { 
            @Override 
            public Function apply(final Integer y) { 
              return new Function() { 
                @Override 
                public Integer apply(Integer t) { 
                  return x + y * t; 
                } 
              }; 
            } 
          }; 
        } 
      }.apply(b).apply(a)); 
    }

以上代碼完全可以實(shí)現(xiàn)所需功能,但是要想說服開發(fā)者,讓他們用這種方式編寫代碼,恐怕非常困難!還好,Java 8的lambda句法提供了以下實(shí)現(xiàn)方式:

private Stream calculate(Stream stream, Integer a) {

      return stream.map(((Function>>) 
                           x -> y -> t -> x + y * t).apply(b).apply(a)); 
    }

怎么樣?或者,是不是可以寫得更簡(jiǎn)單一點(diǎn):

private Stream calculate(Stream stream, Integer a) { 
      return stream.map((x -> y -> t -> x + y * t).apply(b).apply(a)); 
    }

完全可以,但是Java 8不能自行判斷參數(shù)類型,因此我們必須使用manifest類型來幫助確認(rèn)(manifest在Java規(guī)范中的意思是explicit)。為了讓代碼看起來更整潔,我們可以使用一些小技巧:

interface F3 extends Function>> {} 
    private Stream calculate(Stream stream, Integer a) { 
      return stream.map(((F3) x -> y -> z -> x + y * z).apply(b).apply(a)); 
    }

現(xiàn)在,我們來為函數(shù)命名,并在必要時(shí)重用它:

private Stream calculate(Stream stream, Integer a) { 
      F3 calculation = x -> y -> z -> x + y * z; 
      return stream.map(calculation.apply(b).apply(a)); 
    }

我們還可以聲明計(jì)算函數(shù)為一個(gè)輔助類的靜態(tài)成員,使用靜態(tài)導(dǎo)入來進(jìn)一步簡(jiǎn)化代碼:

public class Functions { 
      static Function>> calculation = 
           x -> y -> z -> x + y * z; 
        } 
        ... 
 
        import static Functions.calculation; 
        private Stream calculate(Stream stream, Integer a) { 
          return stream.map(calculation.apply(b).apply(a)); 
        }

可惜,Java 8 鼓勵(lì)的是使用閉包。不然,我會(huì)介紹更多能讓局部套用的使用更為簡(jiǎn)便的功能性語(yǔ)法糖。比如,在Scala中,以上例子就可以這樣改寫:

stream.map(calculation(b)(a))

雖然在Java中我們沒法這樣寫??墒牵ㄟ^下面的靜態(tài)方法,我們可以達(dá)到相似的效果:

static Function>> calculation 
        = x -> y -> z -> x + y * z; 
    static Function calculation(Integer x, Integer y) { 
      return calculation.apply(x).apply(y); 
    }

現(xiàn)在,我們可以寫:

 private Stream calculate(Stream stream, Integer a) { 
      return stream.map(calculation(b, a)); 
    }

請(qǐng)注意,calculation(b, a)不是帶有兩個(gè)參數(shù)的函數(shù)。它只是一個(gè)方法,在將兩個(gè)參數(shù)逐一地局部調(diào)用至一個(gè)帶有三個(gè)參數(shù)的函數(shù)之后,它會(huì)返回一個(gè)帶有一個(gè)參數(shù)的函數(shù),該函數(shù)便可傳遞給映射函數(shù)。

現(xiàn)在,calculation方法便可以多帶帶測(cè)試了。

自動(dòng)局部調(diào)用

在之前的例子中,我們已經(jīng)親手實(shí)踐過局部調(diào)用了。然而,我們大可以編寫程序來自動(dòng)化調(diào)用過程。我們可以編寫這樣一個(gè)方法:它會(huì)接收帶有兩個(gè)參數(shù)的函數(shù),并返回該函數(shù)的局部調(diào)用版本。寫起來非常簡(jiǎn)單:

public  Function> curry(final BiFunction f) { 
      return (A a) -> (B b) -> f.apply(a, b); 
    }

有必要的話,我們還可以寫一個(gè)方法來顛倒這一過程。這個(gè)過程可以接受A的Function函數(shù)作為參數(shù),返回一個(gè)可返回C的B的Function函數(shù),最終返回一個(gè)返回C的A,B的BiFunction函數(shù)。

public  BiFunction uncurry(Function> f) { 
      return (A a, B b) -> f.apply(a).apply(b); 
    }
局部調(diào)用的其他應(yīng)用

局部調(diào)用的應(yīng)用方式還有很多。最重要的應(yīng)用是模擬多參數(shù)函數(shù)。在Java 8提供了單參數(shù)函數(shù)(java.util.functions.Function)以及雙參數(shù)函數(shù)(java.util.functions.BiFunction)。但并未提供存在于其他語(yǔ)言中的三參數(shù)、四參數(shù)、五參數(shù)甚至更多參數(shù)的函數(shù)。其實(shí),有沒有這些函數(shù)并不重要。它們只是在特定情況下,需要同時(shí)對(duì)所有參數(shù)取值時(shí)應(yīng)用的語(yǔ)法糖。實(shí)際上,這也是BiFunctin在Java 8中存在的原因:函數(shù)的常見使用方法就是模擬二元運(yùn)算符,(請(qǐng)注意:在Java 8中有BinaryOperator接口,但它只用于兩個(gè)參數(shù)以及返回值都屬于同一類型的特殊情況。我們將在下一篇文章中討論這一點(diǎn)。)

局部調(diào)用在函數(shù)的各個(gè)參數(shù)需要在不同地方取值時(shí)是非常好用的。通過局部調(diào)用,我們可以在某一組件中對(duì)一個(gè)參數(shù)取值,然后將計(jì)算結(jié)果傳遞到另一組件對(duì)其他參數(shù)取值,如此反復(fù),直到所有參數(shù)值都被取到。

小結(jié)

Java 8并不是一種函數(shù)式語(yǔ)言(可能永遠(yuǎn)也不會(huì)是)。但是,我們?nèi)钥梢栽贘ava(甚至是Java 8之前的版本)中使用函數(shù)式范式。這樣做的確會(huì)略有代價(jià)。但這種代價(jià)在Java 8中已經(jīng)大幅減少了。盡管如此,想要寫函數(shù)型代碼的開發(fā)者還是得動(dòng)動(dòng)腦筋才能掌握這種范式。使用局部調(diào)用就是智力成果之一。

請(qǐng)記住:

(A, B, C) -> D

總是可以由如下方式替代:

A -> B -> C -> D

即便Java 8無(wú)法判斷該表達(dá)方式的類型,你只要自行指定其類型就可以了。這就是局部調(diào)用,它總是比閉包更為穩(wěn)妥。

OneAPM 能為您提供端到端的 Java 應(yīng)用性能解決方案,我們支持所有常見的 Java 框架及應(yīng)用服務(wù)器,助您快速發(fā)現(xiàn)系統(tǒng)瓶頸,定位異常根本原因。分鐘級(jí)部署,即刻體驗(yàn),Java 監(jiān)控從來沒有如此簡(jiǎn)單。想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問 OneAPM 官方技術(shù)博客。
本文轉(zhuǎn)自 OneAPM 官方博客
編譯自:https://dzone.com/articles/whats-wrong-java-8-currying-vs

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65857.html

相關(guān)文章

  • 函數(shù)式編程中局部應(yīng)用(Partial Application)和局部套用(Currying)的區(qū)別

    摘要:真正留給我們要實(shí)現(xiàn)的僅僅是返回另外一部分用于局部應(yīng)用的一元函數(shù)罷了??偨Y(jié)各用一句話做個(gè)小結(jié)吧局部應(yīng)用是一種轉(zhuǎn)換技巧,通過預(yù)先傳入一個(gè)或多個(gè)參數(shù)來把多元函數(shù)轉(zhuǎn)變?yōu)楦僖恍┰暮瘮?shù)甚或是一元函數(shù)。 局部應(yīng)用(Partial Application,也譯作偏應(yīng)用或部分應(yīng)用)和局部 套用( Currying, 也譯作柯里化),是函數(shù)式編程范式中很常用的技巧。 本文著重于闡述它們的...

    AaronYuan 評(píng)論0 收藏0
  • javascript---閉包

    摘要:要理解閉包,首先必須理解特殊的變量作用域。使用閉包有一個(gè)優(yōu)點(diǎn),也是它的缺點(diǎn)就是可以把局部變量駐留在內(nèi)存中,可以避免使用全局變量。 js閉包 閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù),創(chuàng)建閉包的常見的方式,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù),通過另一個(gè)函數(shù)訪問這個(gè)函數(shù)的局部變量。要理解閉包,首先必須理解Javascript特殊的變量作用域。變量的作用域無(wú)非就是兩種:全局變量和局部變量...

    stormgens 評(píng)論0 收藏0
  • 理解JS閉包(Closure)

    摘要:由于各種原因,我們需要在函數(shù)的外部調(diào)用函數(shù)內(nèi)部定義的局部變量。閉包的主要用處是把函數(shù)內(nèi)部的變量一直保存在內(nèi)存中可以省略該局部變量一直保存在內(nèi)存中該函數(shù)被賦予給全局變量,所以一直存在,該函數(shù)的外層函數(shù)因此也一直存在舉例 由于各種原因,我們需要在函數(shù)的外部調(diào)用函數(shù)內(nèi)部定義的局部變量。 閉包實(shí)際上就是函數(shù)內(nèi)部的函數(shù),通過在函數(shù)內(nèi)部再定義一個(gè)函數(shù),內(nèi)部函數(shù)返回函數(shù)的局部變量,函數(shù)再返回內(nèi)部函數(shù)...

    galaxy_robot 評(píng)論0 收藏0
  • js基礎(chǔ)深入淺出

    摘要:當(dāng)多個(gè)事件觸發(fā)的時(shí)候,會(huì)把異步事件依次的放入里等同步事件執(zhí)行完之后,再去隊(duì)列里一個(gè)個(gè)執(zhí)行拾遺常用方法總結(jié)面試的信心來源于過硬的基礎(chǔ)參考高級(jí)程序設(shè)計(jì)你所不知道的深入淺出知識(shí)點(diǎn)思維導(dǎo)圖經(jīng)典實(shí)例總結(jié)那些剪不斷理還亂的關(guān)系 持續(xù)不斷更新。。。 基本類型和引用類型 vue props | Primitive vs Reference Types 基本類型和字面值之間的區(qū)別 基本類型和字面值相等,...

    phodal 評(píng)論0 收藏0
  • 面試官問我:什么是JavaScript閉包,我該如何回答

    摘要:到底什么是閉包這個(gè)問題在面試是時(shí)候經(jīng)常都會(huì)被問,很多小白一聽就懵逼了,不知道如何回答好。上面這么說閉包是一種特殊的對(duì)象。閉包的注意事項(xiàng)通常,函數(shù)的作用域及其所有變量都會(huì)在函數(shù)執(zhí)行結(jié)束后被銷毀。從而使用閉包模塊化代碼,減少全局變量的污染。 閉包,有人說它是一種設(shè)計(jì)理念,有人說所有的函數(shù)都是閉包。到底什么是閉包?這個(gè)問題在面試是時(shí)候經(jīng)常都會(huì)被問,很多小白一聽就懵逼了,不知道如何回答好。這個(gè)...

    BenCHou 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<