国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Ummm... Java8和lambda

LMou / 2095人閱讀

摘要:引入了與此前完全不同的函數式編程方法,通過表達式和來為下的函數式編程提供動力。命令式編程語言把對象變量和流轉當作一等公民,而函數式編程在此基礎上加入了策略變量這一新的一等公民。

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

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

到底什么是函數式編程呢?

函數式編程并不是一個新概念,諸如Haskell這樣的學院派編程語言就是以函數式編程為根基的,JVM平臺上更徹底的采用函數式編程思維的更是以Scala為代表,因此函數式編程確實不是什么新概念。
下面來談談命令式編程和函數式編程。
什么是命令式編程呢?很容易理解,就是一條條的命令明明白白地告訴計算機,計算機依照這些這些明確的命令一步步地執行下去就好了,從匯編到C,這樣的命令式編程語言無非都是在模仿計算機的機器指令的下達,明確地在每一句命令里面告訴計算機每一步需要怎么申請內存(對象變量)、怎么跳轉到下一句命令(流轉),即便后來的為面向對象編程思維而生的編程語言,比如Java,也仍然未走出這個范式,在每個類的對象執行具體的方法時也是按照這種“對象變量-流轉”的模式在運行的。在這個模式下,我們會經常發現程序編寫可能會經常限于冗長的“非關鍵”語句,大量的無用命令只是為了照顧語言本身的規則:比如所謂的面向接口編程最終變成了定義了一組一組的interface、interfaceImpl。
函數式編程則試圖從編程范式的高度提高代碼的抽象表達能力。命令式編程語言把“對象變量”和“流轉”當作一等公民,而函數式編程在此基礎上加入了“策略變量”這一新的一等公民。策略是什么呢?策略就是函數,函數本身是可以作為變量進行傳遞的。在以往的編程范式里,策略要被使用時通常是被調用,所以策略的使用必須通過承載策略的類或對象這樣的對象變量,而函數式編程里面,我們可以直接使用策略對象來隨意傳遞,省去了這些不必要的無用命令。
Java8作為一個新特性版本,在保留原有的Java純面向對象特性之外,在容易理解的范圍內引入了函數式編程方式。

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

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

首先我們定義蘋果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;
    }
}

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

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)));
        }
}
純命令式的思路:

如果我們使用傳統的命令式的編程方法,這個從蘋果堆中篩選紅蘋果的方法會這樣:

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);

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

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);
面向對象的編程方法:

使用為抽象操作而生的接口:接口只定義抽象的方法,具體的方法實現可以有不同的類來實現。如果把這些操作放到繼承了一般篩選器的不同篩選方法的篩選器中去就會有一個典型的面向對象式的解決方案了:

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;
    }
}
面向對象的抽象層級問題

我們發現雖然使用了面向對象的編程方法雖然可以使得邏輯結構更為清晰:子類蘋果篩選器實現了一般蘋果篩選器的抽象方法,但仍然會有大量的代碼是出現多次的。
這就是典型的壞代碼的味道,重復編寫了兩個基本一樣的代碼,所以我們要怎么修改才能使得代碼應對變化的需求呢,比如可以應對篩選其他顏色的蘋果,不要某些顏色的蘋果,可以篩選某些重量范圍的蘋果等等,而不是每個確定的篩選都需要編寫獨立且基本邏輯相同的代碼呢?

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

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

不重復的代碼有哪些:

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

其實對于循環列表這部分是對篩選這一邏輯的公用代碼,而真正不同的是篩選的具體邏輯:根據紅色篩選、綠色篩選等等。

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

傳遞策略對象

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

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

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

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

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

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

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

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的匿名類來實現選擇器接口,我們可以不顯式地定義RedAppleTester,BigAppleTester,而進一步簡潔代碼:

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;            
            }
        });

所以我們已經從上面的說明中看到,我們定義的策略是:一個實現了一般蘋果選擇器接口的抽象方法的特殊蘋果選擇器類的對象,因為是對象,所以當然是可以在代碼里作為參數來傳遞的。這也就是我們反復提到的在函數式編程里的策略傳遞,在原書中叫做「行為參數化的目的是傳遞代碼」

說到這里,其實這種函數式編程的解決思路并未出現什么Java8的新特性,在低版本的Java上即可實現這個過程,因為思路雖然很繞,但是說到底使用的就是簡單的接口實現和方法重寫。實際上呢,借助Java 8新的特性,我們可以更方便地使用語法糖來編寫更簡潔、更易懂的代碼。

Java 8 Lambda簡潔化函數式編程

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

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

函數式接口的這個方法就是這個函數式接口的函數,這個函數的「參數-返回值」類型描述叫做函數描述符,test函數的描述符是 Apple->Boolean
而lambda表達式其實是一種語法糖現象,它是對函數實現的簡單表述,比如我們上文的一個函數實現,即實現了AppleTester接口的RedAppleTester:

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

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

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

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

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

我們會發現isRedApple方法的方法描述符和函數式接口AppleTester定義的單一抽象方法的函數描述符是一樣的:Apple->Boolean,因此我們可以采用一種叫做方法引用的語法糖來進一步化簡這個lambda表達式,不需要在lambda表達式中重復寫已經定義過的方法:

AppleTester tester = AppleUtils::isRedApple

方法引用之所以可以起作用,就是因為這個被引用的方法具有和引用它的函數式接口的函數描述符相同的方法描述符。在實際創建那個實現了抽象方法的匿名類對象時會將被引用的方法體嵌入到這個實現方法中去:

雖然寫起來簡潔了,但是在本質上編譯器會將lambda表達式編譯成一個這樣的實現了接口抽象方法的匿名類的對象。
基于lambda表達式簡潔而強大的表達能力,可以很容易把上面的這段代碼:

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前無法企及的簡潔程度。

Java 8泛型函數式接口

我們在上文介紹的這個函數式接口:

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

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

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

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

同時我們需要一個泛化的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);

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

java.util.function.Predicate
函數描述符:T->boolean

java.util.function.Consumer
函數描述符:T->void

java.util.function.Function
函數描述符:T->R

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

java.util.function.UnaryOperator
函數描述符:T->T

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

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

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

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

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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69800.html

相關文章

  • 轉 | Java8初體驗(一)lambda表達式語法

    摘要:初體驗下面進入本文的正題表達式。接下來展示表達式和其好基友的配合。吐槽一下方法引用表面上看起來方法引用和構造器引用進一步簡化了表達式的書寫,但是個人覺得這方面沒有的下劃線語法更加通用。 感謝同事【天錦】的投稿。投稿請聯系 tengfei@ifeve.com 本文主要記錄自己學習Java8的歷程,方便大家一起探討和自己的備忘。因為本人也是剛剛開始學習Java8,所以文中肯定有錯誤和理解偏...

    Lucky_Boy 評論0 收藏0
  • 樂字節-Java8新特性-Lambda表達式

    摘要:很多語言等從設計之初就支持表達式。注意此時外部局部變量將自動變為作為方法返回值例子返回判斷字符串是否為空判斷字符串是否為空今天關于新特性表達式就講到這里了,接下來我會繼續講述新特性之函數式接口。 上一篇文章我們了解了Java8新特性-接口默認方法,接下來我們聊一聊Java8新特性之Lambda表達式。 Lambda表達式(也稱為閉包),它允許我們將函數當成參數傳遞給某個方法,或者把代碼...

    gggggggbong 評論0 收藏0
  • Java8-1-初識Lambda表達式與函數式接口

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

    jzman 評論0 收藏0
  • 樂字節-Java8核心特性實戰之Lambda表達式

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

    Karuru 評論0 收藏0
  • Java8實戰》-第三章讀書筆記(Lambda表達式-01)

    摘要:之前,使用匿名類給蘋果排序的代碼是的,這段代碼看上去并不是那么的清晰明了,使用表達式改進后或者是不得不承認,代碼看起來跟清晰了。這是由泛型接口內部實現方式造成的。 # Lambda表達式在《Java8實戰》中第三章主要講的是Lambda表達式,在上一章節的筆記中我們利用了行為參數化來因對不斷變化的需求,最后我們也使用到了Lambda,通過表達式為我們簡化了很多代碼從而極大地提高了我們的...

    longshengwang 評論0 收藏0

發表評論

0條評論

LMou

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<