日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

(3)lambda与函数式——响应式Spring的道法术器

發布時間:2024/1/18 javascript 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (3)lambda与函数式——响应式Spring的道法术器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本系列文章索引:《響應式Spring的道法術器》
前情提要: 什么是響應式編程 | 響應式流
本文源碼

1.3 Hello,reactive world

前面兩篇文章介紹了響應式編程和響應式流的特性,一味講概念終是枯燥,還是上手敲一敲代碼實在感受一下響應式編程的“手感”吧。

這一節,我們先了解一下lambda與函數式(已經了解的朋友可以直接跳到1.3.2),熟悉一下如何使用Reactor進行響應式編程,然后使用Spring Boot2,基于Spring 5的Webflux和Reactive Spring Data逐步開發一個“Hello world”級別的RESTful service。

1.3.1 lambda與函數式

在響應式編程中,lambda與函數式的出鏡率相當高,以至于網上經常有朋友直接用“函數響應式編程”用在“響應式編程”的介紹中。這兩個詞的異同一直存在爭議,其區別雖然不像“JavaScript與Java”、“雷鋒塔與雷峰”那么大,但隨便混用還是會顯得非常不專業:

  • 函數響應式編程的重點在于“函數式”的語言特性,這個概念在二十年前就蓋棺定論了。
  • 響應式編程的重點在于“基于事件流”的異步編程范式,由不斷產生的數據/時間來推動邏輯的執行。

本系列文章討論的都是“響應式編程”,關于“函數響應式編程”,你就當沒聽過,并謹慎地使用它就好了。

1.3.1.1 lambda表達式

書回正傳,為什么響應式編程中會經常用到lambda與函數式呢?不知你對1.1.3節的一段偽代碼是否還有印象:

cartEventStream// 分別計算商品金額.map(cartEvent -> cartEvent.getProduct().getPrice() * cartEvent.getQuantity())...

cartEventStream是一個數據流,其中的元素就是一個一個的cartEvent,map方法能夠對cartEvent進行“轉換/映射”,這里我們將其轉換為double類型的金額。

除了轉換/映射(map)外,還有過濾(filter)、提供(supply)、消費(consume)等等針對流中元素的操作邏輯/策略,而邏輯/策略通常用方法來定義。

在Java 8之前,這就有些麻煩了。我們知道,Java是面向對象的編程語言,除了少數的原生類型外,一切都是對象。用來定義邏輯/策略的方法不能獨立存在,必須被包裝在一個對象中。比如我們比較熟悉的Comparator,其唯一的方法compare表示一種比較策略,在使用的時候,需要包裝在一個對象中傳遞給使用該策略的方法。舉例說明(源碼):

@Test public void StudentCompareTest() {@Data @AllArgsConstructor class Student { // 1private int id;private String name;private double height;private double score;}List<Student> students = new ArrayList<>();students.add(new Student(10001, "張三", 1.73, 88));students.add(new Student(10002, "李四", 1.71, 96));students.add(new Student(10003, "王五", 1.85, 88));class StudentIdComparator<S extends Student> implements Comparator<S> { // 2@Overridepublic int compare(S s1, S s2) {return Integer.compare(s1.getId(), s2.getId());}}students.sort(new StudentIdComparator<>());System.out.println(students); }
  • @Data和@AllArgsConstructor是lombok提供的注解,能夠在編譯的字節碼中生成構造方法、getter/setter、toString等方法。依賴如下:

    <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.20</version> </dependency>
  • 注:本節及以后,關于maven的依賴,可自行至maven搜索庫中查詢新的合適版本。

  • StudentIdComparator中固化了一種針對Student.id的比較策略,當對students進行排序的時候,將StudentIdComparator的對象傳給sort方法。輸出順序如下:

    [Student(id=10001, name=張三, height=1.73, score=88.0), Student(id=10002, name=李四, height=1.71, score=96.0), Student(id=10003, name=王五, height=1.85, score=88.0)]

  • 假設這時候我們需要對學生的身高或分數進行排序,再定義Comparator的實現類有些麻煩了,而且沒必要,“傳統”的簡化方式是直接傳入匿名內部類:

    students.sort(new Comparator<Student>() {@Overridepublic int compare(Student s1, Student s2) {return Double.compare(s1.getHeight(), s2.getHeight());}});

    但其實,我們會發現,無論哪種比較策略,只有compare方法內的代碼發生變化,也就是說sort方法關心的只是傳入的兩個參數Student s1, Student s2以及返回的結論return Double.compare(s1.getHeight(), s2.getHeight())這一句比較策略,何不只保留它們呢?

    students.sort((Student s1, Student s2) -> {return Double.compare(s1.getHeight(), s2.getHeight());});

    這樣看起來代碼就少多了。其中(Student s1, Student s2) -&gt; {return Double.compare(s1.getHeight(), s2.getHeight())}就是lambda表達式,lambda表達式的語法如下:

    (type1 arg1, type2 arg2...) -> { body }

    -&gt;前后分別表示參數和方法體。從代碼編寫方式上來說,這就可以算作是“函數式”編程范式了,因為我們傳給sort的是一個lambda表達式的形式定義的“函數”,這個“函數”有輸入和輸出,在開發者看起來是赤裸裸的,沒有使用對象封裝起來的。

    “函數式”編程范式的核心特點之一:函數是"一等公民"。
    所謂"一等公民"(first class),指的是函數與其他數據類型一樣,處于平等地位,可以賦值給其他變量,也可以作為參數,傳入另一個函數,或者作為別的函數的返回值。

    但也僅僅是“看起來”是“函數式”的了,Java終究是面向對象的語言,List.sort的方法定義仍然是接受一個Comparator對象作為參數的。但是一定要糾結Java是不是純正的函數式語言嗎?沒這個必要,實用至上嘛。

    既然如此,問題來了,sort是如何將這個lambda“看做”一個Comparator對象的呢?

    不難發現,Comparator接口僅有一個抽象方法,因此sort也就不難“推斷”lambda所定義的輸入參數和方法體表示的正是這個唯一的抽象方法compare。

    1.3.1.2 函數式接口

    像Comparator這樣的只有一個抽象方法的接口,叫做函數式接口(Functional Interface)。與Comparator類似,其他函數式接口的唯一的抽象方法也可以用lambda來表示。

    我們看一下Comparator的源碼,發現其多了一個@FunctionalInterface的注解,用來表明它是一個函數式接口。標記了該注解的接口有且僅有一個抽象方法,否則會報編譯錯誤。

    再看一下其他的僅有一個抽象方法的接口,比如Runnable和Callable,發現也都在Java 8之后加了@FunctionalInterface注解。對于Runnable來說,接口定義如下:

    @FunctionalInterface public interface Runnable {public abstract void run(); }

    不難推測,其lambda的寫法應該是 () -&gt; { body },它不接收任何參數,方法體中也無return返回值,用起來像這樣:

    new Thread(() -> {doSomething();});

    此外,隨lambda一同增加的還有一個java.util.function包,其中定義了一些常見的函數式接口的。比如:

    • Function,接受一個輸入參數,返回一個結果。參數與返回值的類型可以不同,我們之前的map方法內的lambda就是表示這個函數式接口的;
    • Consumer,接受一個輸入參數并且無返回的操作。比如我們針對數據流的每一個元素進行打印,就可以用基于Consumer的lambda;
    • Supplier,無需輸入參數,只返回結果。看接口名就知道是發揮了對象工廠的作用;
    • Predicate,接受一個輸入參數,返回一個布爾值結果。比如我們在對數據流中的元素進行篩選的時候,就可以用基于Predicate的lambda;
    • ...

    1.3.1.3 簡化的lambda

    以lambda作為參數的方法能夠推斷出來lambda所表示的是哪個函數式接口的那個抽象方法。類似地,編譯期還可以做更多的推斷。我們再回到最初的Comparator的例子并繼續簡化如下lambda表達式:

    students.sort((Student s1, Student s2) -> {return Double.compare(s1.getHeight(), s2.getHeight());});

    1)首先,傳入的參數類型是可以推斷出來的。因為students是以Student為元素的數組List&lt;Student&gt;,其sort方法自然接收Comparator&lt;? super Student&gt;的對象作為參數,這一切都可以通過泛型約束。

    students.sort((s1, s2) -> {return Double.compare(s1.getHeight(), s2.getHeight());});

    2)如果只有一個return語句的話,return和方法體的大括號都可以省略(compare方法的返回值就是lambda返回值):

    students.sort((s1, s2) -> Double.compare(s1.getHeight(), s2.getHeight()));

    3)注意到,Comparator接口還提供了豐富的靜態方法,比如:

    public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {Objects.requireNonNull(keyExtractor);return (Comparator<T> & Serializable)(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2)); }

    這個方法為我們包裝好了Double.compare。它接收一個返回類型為Double的函數式接口ToDoubleFunction&lt;? super T&gt;,可以看做是Function&lt;? super T, Double&gt;,用lambda表示的話就是student -&gt; student.getHeight()。

    因此,我們的sort方法又可以寫作:

    students.sort(Comparator.comparingDouble((student) -> student.getHeight()));

    其一,對于只有一個參數的lambda來說,參數外邊的小括號可以省略:

    students.sort(Comparator.comparingDouble(student -> student.getHeight()));

    其二,對于僅有一個方法調用的lambda方法體來說,通常又可以用類::方法進一步簡化,以上代碼又可以進一步簡化為:

    students.sort(Comparator.comparingDouble(Student::getScore));

    這里是調用參數所代表對象的某個方法,與之類似的還有比如:

    • string -&gt; System.out.println(string),可以簡化為System.out::println,這里是將參數作為System.out::println的參數了;
    • () -&gt; new HashMap&lt;&gt;(),可以簡化為HashMap::new,這里沒有參數,也可以進行簡化。

    使用類::方法這種寫法是不是更加有函數式的感覺了呢,似乎真是把函數作為參數傳遞給某個方法了呢~

    就不再繼續舉例了,以上這些形形×××的簡化你可能會感覺難以記憶,其實無需記憶,多數IDE都能夠提供簡化建議的。

    1.3.1.4 總結

    在編程語言的世界里,Java就像是一個穩健的中年人,它始終將語言的向后兼容性和穩定性放在首位,不會隨隨便便因為某種語言特性或語法糖就心動,但是對于有顯著預期收益的語言特性也會果斷出擊,泛型如此,lambda亦是如此,或許對它們的引入都不夠徹底和完美,但卻足夠實用,能夠給開發者帶來很大便利。這應該也是Java語言能夠持續保持活力的原因之一吧!

    至于函數式方面更加復雜的概念,這里就不多介紹了。下面我們就認識一下Reactor吧~

    轉載于:https://blog.51cto.com/liukang/2090187

    總結

    以上是生活随笔為你收集整理的(3)lambda与函数式——响应式Spring的道法术器的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。