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

資訊專欄INFORMATION COLUMN

Ummm... Java8和lambda

LMou / 2118人閱讀

摘要:引入了與此前完全不同的函數(shù)式編程方法,通過表達(dá)式和來為下的函數(shù)式編程提供動(dòng)力。命令式編程語言把對(duì)象變量和流轉(zhuǎn)當(dāng)作一等公民,而函數(shù)式編程在此基礎(chǔ)上加入了策略變量這一新的一等公民。

Java8引入了與此前完全不同的函數(shù)式編程方法,通過Lambda表達(dá)式和StreamAPI來為Java下的函數(shù)式編程提供動(dòng)力。本文是Java8新特性的第一篇,旨在闡釋函數(shù)式編程的本義,更在展示Java是如何通過新特性實(shí)現(xiàn)函數(shù)式編程的。

最近在讀這本圖靈的新書:Java 8 in Action ,本書作者的目的在于向Java程序員介紹Java8帶來的新特性,鼓勵(lì)使用新特性來完成更簡(jiǎn)潔有力的Java編程。本系列的文章的主要思路也來源于本書。

到底什么是函數(shù)式編程呢?

函數(shù)式編程并不是一個(gè)新概念,諸如Haskell這樣的學(xué)院派編程語言就是以函數(shù)式編程為根基的,JVM平臺(tái)上更徹底的采用函數(shù)式編程思維的更是以Scala為代表,因此函數(shù)式編程確實(shí)不是什么新概念。
下面來談?wù)劽钍骄幊毯秃瘮?shù)式編程。
什么是命令式編程呢?很容易理解,就是一條條的命令明明白白地告訴計(jì)算機(jī),計(jì)算機(jī)依照這些這些明確的命令一步步地執(zhí)行下去就好了,從匯編到C,這樣的命令式編程語言無非都是在模仿計(jì)算機(jī)的機(jī)器指令的下達(dá),明確地在每一句命令里面告訴計(jì)算機(jī)每一步需要怎么申請(qǐng)內(nèi)存(對(duì)象變量)、怎么跳轉(zhuǎn)到下一句命令(流轉(zhuǎn)),即便后來的為面向?qū)ο缶幊趟季S而生的編程語言,比如Java,也仍然未走出這個(gè)范式,在每個(gè)類的對(duì)象執(zhí)行具體的方法時(shí)也是按照這種“對(duì)象變量-流轉(zhuǎn)”的模式在運(yùn)行的。在這個(gè)模式下,我們會(huì)經(jīng)常發(fā)現(xiàn)程序編寫可能會(huì)經(jīng)常限于冗長(zhǎng)的“非關(guān)鍵”語句,大量的無用命令只是為了照顧語言本身的規(guī)則:比如所謂的面向接口編程最終變成了定義了一組一組的interface、interfaceImpl。
函數(shù)式編程則試圖從編程范式的高度提高代碼的抽象表達(dá)能力。命令式編程語言把“對(duì)象變量”和“流轉(zhuǎn)”當(dāng)作一等公民,而函數(shù)式編程在此基礎(chǔ)上加入了“策略變量”這一新的一等公民。策略是什么呢?策略就是函數(shù),函數(shù)本身是可以作為變量進(jìn)行傳遞的。在以往的編程范式里,策略要被使用時(shí)通常是被調(diào)用,所以策略的使用必須通過承載策略的類或?qū)ο筮@樣的對(duì)象變量,而函數(shù)式編程里面,我們可以直接使用策略對(duì)象來隨意傳遞,省去了這些不必要的無用命令。
Java8作為一個(gè)新特性版本,在保留原有的Java純面向?qū)ο筇匦灾猓谌菀桌斫獾姆秶鷥?nèi)引入了函數(shù)式編程方式。

引入策略:策略何以作為變量?

我們有這樣的一個(gè)引入的例子:我們有一堆顏色和重量不定的蘋果,這些蘋果需要經(jīng)過我們的一道程序,這道程序可以把這堆蘋果中的紅蘋果取出來。怎樣編寫程序來選出紅蘋果呢?

首先我們定義蘋果Apple類:

public class Apple{
    private String color;
    private Integer weight;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public Integer getWeight() {
        return weight;
    }

    public void setWeight(Integer weight) {
        this.weight = weight;
    }

    public Apple(String color, Integer weight) {
        this.color = color;
        this.weight = weight;
    }
}

添加我們的一堆顏色和重量隨機(jī)的蘋果:

public static void main(String[] args){
        ArrayList apples = new ArrayList<>();
        Random weightRandom = new Random();
        Random colorRandom = new Random();
        String[] colors = {"red","green","yellow"};
        for (int i = 0; i < 100; i++) {
            apples.add(new Apple(colors[colorRandom.nextInt(3)],weightRandom.nextInt(200)));
        }
}
純命令式的思路:

如果我們使用傳統(tǒng)的命令式的編程方法,這個(gè)從蘋果堆中篩選紅蘋果的方法會(huì)這樣:

public static List redAppleFilter(List apples){
        List redApples = new ArrayList<>();
        for (Apple apple:
             apples) {
            if("red".equals(apple.getColor())){
                redApples.add(apple);
            }
        }
        return redApples;
    }
List redApples = redAppleFilter(apples);

如果這個(gè)時(shí)候我們變更需求了,比如我們不篩選紅蘋果了,要綠蘋果了,怎么辦呢?就得再定義一個(gè)從蘋果堆中篩選綠蘋果的方法:

public static List greenAppleFilter(List apples){
        List greenApples = new ArrayList<>();
        for (Apple apple:
             apples) {
            if("green".equals(apple.getColor())){
                greenApples.add(apple);
            }
        }
        return greenApples;
    }
List greenApples = greenAppleFilter(apples);
面向?qū)ο蟮木幊谭椒ǎ?/b>

使用為抽象操作而生的接口:接口只定義抽象的方法,具體的方法實(shí)現(xiàn)可以有不同的類來實(shí)現(xiàn)。如果把這些操作放到繼承了一般篩選器的不同篩選方法的篩選器中去就會(huì)有一個(gè)典型的面向?qū)ο笫降慕鉀Q方案了:

interface AppleFilter {
    public List filterByRules(List apples);
}

class RedAppleFilter implements AppleFilter{

    @Override
    public List filterByRules(List apples) {
        List redApples = new ArrayList<>();
        for (Apple apple:
                apples) {
            if("red".equals(apple.getColor())){
                redApples.add(apple);
            }
        }
        return redApples;
    }
}

class GreenAppleFilter implements AppleFilter{

    @Override
    public List filterByRules(List apples) {
        List greenApples = new ArrayList<>();
        for (Apple apple:
                apples) {
            if("green".equals(apple.getColor())){
                greenApples.add(apple);
            }
        }
        return greenApples;
    }
}
面向?qū)ο蟮某橄髮蛹?jí)問題

我們發(fā)現(xiàn)雖然使用了面向?qū)ο蟮木幊谭椒m然可以使得邏輯結(jié)構(gòu)更為清晰:子類蘋果篩選器實(shí)現(xiàn)了一般蘋果篩選器的抽象方法,但仍然會(huì)有大量的代碼是出現(xiàn)多次的。
這就是典型的壞代碼的味道,重復(fù)編寫了兩個(gè)基本一樣的代碼,所以我們要怎么修改才能使得代碼應(yīng)對(duì)變化的需求呢,比如可以應(yīng)對(duì)篩選其他顏色的蘋果,不要某些顏色的蘋果,可以篩選某些重量范圍的蘋果等等,而不是每個(gè)確定的篩選都需要編寫?yīng)毩⑶一具壿嬒嗤拇a呢?

我們來看一下重復(fù)的代碼究竟是哪些:

List greenApples = new ArrayList<>();
   for (Apple apple:
           apples) {
       ... ...
   }
   return greenApples;

不重復(fù)的代碼有哪些:

if("green".equals(apple.getColor())){
    
}

其實(shí)對(duì)于循環(huán)列表這部分是對(duì)篩選這一邏輯的公用代碼,而真正不同的是篩選的具體邏輯:根據(jù)紅色篩選、綠色篩選等等。

而造成現(xiàn)在局面的原因就在于僅僅對(duì)大的篩選方法的實(shí)現(xiàn)的抽象層級(jí)太低了,所以就會(huì)編寫太多的代碼,如果篩選的抽象層級(jí)定位到篩選策略這一級(jí)就會(huì)大大提升代碼的抽象能力。

傳遞策略對(duì)象

所謂策略的范圍就是我們上面找到的這個(gè)“不重復(fù)的代碼”:在這個(gè)問題里面就是什么樣的蘋果是可以經(jīng)過篩選的。所以我們需要的這個(gè)策略就是用于確定什么樣的蘋果是可以被選出來的。我們定義一個(gè)這樣的接口:給一個(gè)蘋果用于判斷,在test方法里對(duì)這個(gè)蘋果進(jìn)行檢測(cè),然后給出是否被選出的結(jié)果。

interface AppleTester{
    public Boolean test(Apple apple);
}

比如我們可以通過實(shí)現(xiàn)上述接口,重寫這個(gè)test方法使之成為選擇紅蘋果的方法,然后我們就可以得到一個(gè)紅蘋果選擇器:

class RedAppleTester implements AppleTester{
    @Override
    public Boolean test(Apple apple) {
        return "red".equals(apple.getColor());
    }
}

再比如我們可以通過實(shí)現(xiàn)上述接口,重寫這個(gè)test方法使之成為選擇大蘋果的方法,然后我們就可以得到一個(gè)大蘋果選擇器:

class BigAppleTester implements AppleTester{
    @Override
    public Boolean test(Apple apple) {
        return apple.getWeight()>150;
    }
}

有了這個(gè)選擇器,我們就可以把這個(gè)選擇器,亦即我們上面提到的篩選策略,傳給我們的篩選器,以此進(jìn)行相應(yīng)需求的篩選,只要改變選擇器,就可以更換篩選策略:

public static List filterSomeApple(List apples,AppleTester tester){
        ArrayList resList = new ArrayList<>();
        for (Apple apple
                : apples) {
            if(tester.test(apple))
                resList.add(apple);
        }
        return resList;
    }
List redApples = filterSomeApple(apples,new RedAppleTester());
List bigApples = filterSomeApple(apples,new BigAppleTester());

通過使用Java的匿名類來實(shí)現(xiàn)選擇器接口,我們可以不顯式地定義RedAppleTester,BigAppleTester,而進(jìn)一步簡(jiǎn)潔代碼:

List redApples = filterSomeApple(apples, new AppleTester() {
            @Override
            public Boolean test(Apple apple) {
                return "red".equals(apple.getColor());
            }
        });
List bigApples = filterSomeApple(apples, new AppleTester() {
            @Override
            public Boolean test(Apple apple) {
                return apple.getWeight()>150;            
            }
        });

所以我們已經(jīng)從上面的說明中看到,我們定義的策略是:一個(gè)實(shí)現(xiàn)了一般蘋果選擇器接口的抽象方法的特殊蘋果選擇器類的對(duì)象,因?yàn)槭菍?duì)象,所以當(dāng)然是可以在代碼里作為參數(shù)來傳遞的。這也就是我們反復(fù)提到的在函數(shù)式編程里的策略傳遞,在原書中叫做「行為參數(shù)化的目的是傳遞代碼」。

說到這里,其實(shí)這種函數(shù)式編程的解決思路并未出現(xiàn)什么Java8的新特性,在低版本的Java上即可實(shí)現(xiàn)這個(gè)過程,因?yàn)樗悸冯m然很繞,但是說到底使用的就是簡(jiǎn)單的接口實(shí)現(xiàn)和方法重寫。實(shí)際上呢,借助Java 8新的特性,我們可以更方便地使用語法糖來編寫更簡(jiǎn)潔、更易懂的代碼。

Java 8 Lambda簡(jiǎn)潔化函數(shù)式編程

我們上面定義的這種單方法接口叫做函數(shù)式接口

interface AppleTester{
    public Boolean test(Apple apple);
}

函數(shù)式接口的這個(gè)方法就是這個(gè)函數(shù)式接口的函數(shù),這個(gè)函數(shù)的「參數(shù)-返回值」類型描述叫做函數(shù)描述符,test函數(shù)的描述符是 Apple->Boolean。
而lambda表達(dá)式其實(shí)是一種語法糖現(xiàn)象,它是對(duì)函數(shù)實(shí)現(xiàn)的簡(jiǎn)單表述,比如我們上文的一個(gè)函數(shù)實(shí)現(xiàn),即實(shí)現(xiàn)了AppleTester接口的RedAppleTester:

class RedAppleTester implements AppleTester{
    @Override
    public Boolean test(Apple apple) {
            return "red".equals(apple.getColor());
    }
}

這個(gè)實(shí)現(xiàn)類可以用lambda表達(dá)式
(Apple a) -> "red".equals(a.getColor())
或者
(Apple a) -> {return "red".equals(a.getColor());}
來代替。->前是參數(shù)列表,后面是表達(dá)式或命令。

在有上下文的情況下,甚至有更簡(jiǎn)潔的寫法:
AppleTester tester = a -> "red".equals(a.getColor());
可以這樣寫的原因在于編譯器可以根據(jù)上下文來推斷參數(shù)類型:AppleTester作為函數(shù)式接口只定義了單一抽象方法:public Boolean test(Apple apple),所以可以很容易地推斷出其抽象方法實(shí)現(xiàn)的參數(shù)類型。

如果AppleUtils工具類直接定義了判定紅蘋果的方法:

class AppleUtils {
    public static Boolean isRedApple(Apple apple) {
        return "red".equals(apple.getColor());
    }
}

我們會(huì)發(fā)現(xiàn)isRedApple方法的方法描述符和函數(shù)式接口AppleTester定義的單一抽象方法的函數(shù)描述符是一樣的:Apple->Boolean,因此我們可以采用一種叫做方法引用的語法糖來進(jìn)一步化簡(jiǎn)這個(gè)lambda表達(dá)式,不需要在lambda表達(dá)式中重復(fù)寫已經(jīng)定義過的方法:

AppleTester tester = AppleUtils::isRedApple

方法引用之所以可以起作用,就是因?yàn)檫@個(gè)被引用的方法具有和引用它的函數(shù)式接口的函數(shù)描述符相同的方法描述符。在實(shí)際創(chuàng)建那個(gè)實(shí)現(xiàn)了抽象方法的匿名類對(duì)象時(shí)會(huì)將被引用的方法體嵌入到這個(gè)實(shí)現(xiàn)方法中去:

雖然寫起來簡(jiǎn)潔了,但是在本質(zhì)上編譯器會(huì)將lambda表達(dá)式編譯成一個(gè)這樣的實(shí)現(xiàn)了接口抽象方法的匿名類的對(duì)象。
基于lambda表達(dá)式簡(jiǎn)潔而強(qiáng)大的表達(dá)能力,可以很容易把上面的這段代碼:

List redApples = filterSomeApple(apples, new AppleTester() {
            @Override
            public Boolean test(Apple apple) {
                return "red".equals(apple.getColor());
            }
        });

改寫為Java8版本的:

List redApples = filterSomeApple(apples, AppleUtils::isRedApple);

如你所見,這樣的寫法瞬間將代碼改到Java8前無法企及的簡(jiǎn)潔程度。

Java 8泛型函數(shù)式接口

我們?cè)谏衔慕榻B的這個(gè)函數(shù)式接口:

interface AppleTester{
    public Boolean test(Apple apple);
}

它的作用僅僅是對(duì)蘋果進(jìn)行選擇,通過實(shí)現(xiàn)test抽象方法來作出具體的選擇器。
但是其實(shí)在我們的應(yīng)用環(huán)境中,很多需求是泛化的,比如上文中的給一個(gè)對(duì)象(文中是蘋果)以判斷其是否能滿足某些需求,這個(gè)場(chǎng)景一經(jīng)泛化即可被許多場(chǎng)景所使用,可以使用泛型來對(duì)接口進(jìn)行泛化:

interface ChooseStrategy{
    public Boolean test(T t);
}

public Boolean test(T t)的函數(shù)描述符是T->Boolean,所以只要說滿足這個(gè)描述符的方法都可以作為方法引用。

同時(shí)我們需要一個(gè)泛化的filter方法:

    public static  List filter(List ts, ChooseStrategy strategy){
        ArrayList resList = new ArrayList<>();
        for (T t
                : ts) {
            if(strategy.test(t))
                resList.add(t);
        }
        return resList;
    }
List redApples = filter(apples,AppleUtils::isRedApple);

除了這種在類型上的泛型來泛化使用定義的函數(shù)式接口外,甚至有一些公用的場(chǎng)景Java8 為我們定義了一整套的函數(shù)式接口API來涵蓋這些使用場(chǎng)景中需要的函數(shù)式接口。我們的編程中甚至不需要自己定義這些函數(shù)式接口:

java.util.function.Predicate
函數(shù)描述符:T->boolean

java.util.function.Consumer
函數(shù)描述符:T->void

java.util.function.Function
函數(shù)描述符:T->R

java.util.function.Supplier
函數(shù)描述符:()->T

java.util.function.UnaryOperator
函數(shù)描述符:T->T

java.util.function.BinaryOperator
函數(shù)描述符:(T,T)->T

java.util.function.BiPredicate
函數(shù)描述符:(L,R)->boolean

java.util.function.BiConsumer
函數(shù)描述符:(T,U)->void

java.util.function.BiFunction
函數(shù)描述符:(T,U)->R

Java8通過接口抽象方法實(shí)現(xiàn)、lambda表達(dá)式來實(shí)現(xiàn)了策略對(duì)象的傳遞,使得函數(shù)成為了第一公民,并以此來將函數(shù)式編程帶入了Java世界中。
有了策略傳遞后,使用具體的策略來完成任務(wù),比如本文中篩選蘋果的filter過程,Java8則依靠StreamAPI來實(shí)現(xiàn),一系列泛化的任務(wù)過程定義在這些API中,這也將是本系列文章的后續(xù)的關(guān)注。

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

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

相關(guān)文章

  • 轉(zhuǎn) | Java8初體驗(yàn)(一)lambda表達(dá)式語法

    摘要:初體驗(yàn)下面進(jìn)入本文的正題表達(dá)式。接下來展示表達(dá)式和其好基友的配合。吐槽一下方法引用表面上看起來方法引用和構(gòu)造器引用進(jìn)一步簡(jiǎn)化了表達(dá)式的書寫,但是個(gè)人覺得這方面沒有的下劃線語法更加通用。 感謝同事【天錦】的投稿。投稿請(qǐng)聯(lián)系 [email protected] 本文主要記錄自己學(xué)習(xí)Java8的歷程,方便大家一起探討和自己的備忘。因?yàn)楸救艘彩莿倓傞_始學(xué)習(xí)Java8,所以文中肯定有錯(cuò)誤和理解偏...

    Lucky_Boy 評(píng)論0 收藏0
  • 樂字節(jié)-Java8新特性-Lambda表達(dá)式

    摘要:很多語言等從設(shè)計(jì)之初就支持表達(dá)式。注意此時(shí)外部局部變量將自動(dòng)變?yōu)樽鳛榉椒ǚ祷刂道臃祷嘏袛嘧址欠駷榭张袛嘧址欠駷榭战裉礻P(guān)于新特性表達(dá)式就講到這里了,接下來我會(huì)繼續(xù)講述新特性之函數(shù)式接口。 上一篇文章我們了解了Java8新特性-接口默認(rèn)方法,接下來我們聊一聊Java8新特性之Lambda表達(dá)式。 Lambda表達(dá)式(也稱為閉包),它允許我們將函數(shù)當(dāng)成參數(shù)傳遞給某個(gè)方法,或者把代碼...

    gggggggbong 評(píng)論0 收藏0
  • Java8-1-初識(shí)Lambda表達(dá)式與函數(shù)式接口

    摘要:而在中,表達(dá)式是對(duì)象,它們必須依附于一類特別的對(duì)象類型函數(shù)式接口。即表達(dá)式返回的是函數(shù)式接口類型。 Java8被稱作Java史上變化最大的一個(gè)版本。其中包含很多重要的新特性,最核心的就是增加了Lambda表達(dá)式和Stream API。這兩者也可以結(jié)合在一起使用。首先來看下什么是Lambda表達(dá)式。Lambda表達(dá)式,維基百科上的解釋是一種用于表示匿名函數(shù)和閉包的運(yùn)算符,感覺看到這個(gè)解釋...

    jzman 評(píng)論0 收藏0
  • 樂字節(jié)-Java8核心特性實(shí)戰(zhàn)之Lambda表達(dá)式

    摘要:使用表達(dá)式,使得應(yīng)用變得簡(jiǎn)潔而緊湊。很多語言等從設(shè)計(jì)之初就支持表達(dá)式。表達(dá)式的參數(shù)與函數(shù)式接口內(nèi)方法的參數(shù),返回值類型相互對(duì)應(yīng)。更多教程和資料請(qǐng)上騰訊課堂樂字節(jié) showImg(https://segmentfault.com/img/bVbtotg?w=935&h=345); Java8 引入Lambda表達(dá)式,允許開發(fā)者將函數(shù)當(dāng)成參數(shù)傳遞給某個(gè)方法,或者把代碼本身當(dāng)作數(shù)據(jù)進(jìn)行處理。...

    Karuru 評(píng)論0 收藏0
  • Java8實(shí)戰(zhàn)》-第三章讀書筆記(Lambda表達(dá)式-01)

    摘要:之前,使用匿名類給蘋果排序的代碼是的,這段代碼看上去并不是那么的清晰明了,使用表達(dá)式改進(jìn)后或者是不得不承認(rèn),代碼看起來跟清晰了。這是由泛型接口內(nèi)部實(shí)現(xiàn)方式造成的。 # Lambda表達(dá)式在《Java8實(shí)戰(zhàn)》中第三章主要講的是Lambda表達(dá)式,在上一章節(jié)的筆記中我們利用了行為參數(shù)化來因?qū)Σ粩嘧兓男枨螅詈笪覀円彩褂玫搅薒ambda,通過表達(dá)式為我們簡(jiǎn)化了很多代碼從而極大地提高了我們的...

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

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

0條評(píng)論

LMou

|高級(jí)講師

TA的文章

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