第一章 Thinking Reactively(響應式的思考)

第一章 Thinking Reactively(響應式的思考)

假定您相當熟悉Java並且知道如何使用類,接口,方法,屬性,變量,靜態/非靜態作用域和集合。 如果您還沒有完成併發或多線程,那就可以了。 RxJava使這些高級主題更加易於訪問。

準備好您喜歡的Java開發環境,無論是Intellij IDEA,Eclipse,NetBeans還是您選擇的任何其他環境。 我將使用Intellij IDEA,它不會影響本書中的示例。 我建議您也擁有一個構建自動化系統,例如Gradle或Maven,我們將在不久的將來進行逐步介紹。

在深入研究RxJava之前,我們將首先介紹一些核心主題:

1、響應式擴展和RxJava的簡要歷史

2、響應式的思考

3、利用RxJava

4、設置您的第一個RxJava項目

5、構建您的第一個響應式應用程序

6、RxJava 1.0和RxJava 2.0之間的區別

響應式擴展和RxJava的簡要歷史

作爲開發人員,我們傾向於訓練自己以違反直覺的方式思考。用代碼爲我們的世界建模從來都不乏挑戰。不久前,面向對象編程被視爲解決此問題的靈丹妙藥。對我們在現實生活中與之互動的事物進行藍圖是一個革命性的想法,而類和對象的核心概念仍然影響着我們今天的編碼方式。但是,業務和用戶需求繼續變得越來越複雜。隨着2010年的臨近,很明顯,面向對象的編程只能解決部分問題。

類和對象在用屬性和方法表示實體方面做得很好,但是當它們需要以越來越複雜(且常常是計劃外的)的方式相互交互時,它們就會變得混亂。出現了去耦模式和範例,但這產生了越來越多的樣板代碼的不良副作用。針對這些問題,函數式編程開始捲土重來,不是取代面向對象的編程,而是對其進行補充並填補了這一空白。響應式編程(一種功能性的事件驅動編程方法)開始引起人們的特別關注。

最終出現了兩個響應式框架,包括Akka和Sodium。但是在Microsoft,一位名叫Erik Meijer的計算機科學家爲.NET創建了一個名爲Reactive Extensions的響應式編程框架。幾年之內,Reactive Extensions(也稱爲ReactiveX或Rx)已移植到多種語言和平臺,包括JavaScript,Python,C,Swift和Java。 ReactiveX迅速成爲一種跨語言標準,將反應式編程帶入了行業。

RxJava是Java的ReactiveX端口,大部分是由Netflix和David Karnok的Ben Christensen創建的。 RxJava 1.0於2014年11月發佈,其後於2016年11月發佈RxJava2.0。RxJava是其他ReactiveX JVM端口(例如RxScala,RxKotlin和RxGroovy)的骨幹。它已經成爲Android開發的核心技術,並且也已經進入Java後端開發。許多RxJava適配器庫,例如RxAndroid(https://github.com/ReactiveX/RxAndroid)、RxJava-JDBC(https://github.com/davidmoten/rxjava-jdbc)、RxNetty(https://github.com/ReactiveX/RxNetty)和RxJavaFX(https://github.com/ReactiveX/RxJavaFX)適應了多個Java框架,使其變得可響應並可以立即使用RxJava,這一切都表明RxJava不僅僅是一個庫。它是更大的ReactiveX生態系統的一部分,該生態系統代表了整個編程方法。 ReactiveX的基本思想是事件是數據,數據是事件。這是一個非常有力的概念,我們將在本章後面探討,但首先,讓我們退一步,通過響應式鏡頭來審視世界。

響應式的思考

將您對Java(以及一般而言的編程)所掌握的所有知識暫停一下,讓我們對我們的世界進行一些觀察。這些聽起來似乎很明顯,但是作爲開發人員,我們可以輕鬆地忽略它們。讓您注意一切都在運動的事實。交通,天氣,人,對話,財務交易等都在移動。從技術上講,由於地球的自轉和軌道運動,即使是像岩石一樣靜止的東西也正在運動。當您考慮將所有事物建模爲動態模型的可能性時,作爲開發人員,您可能會覺得有些不知所措。

要注意的另一個觀察結果是這些不同的事件是同時發生的,同時有多個活動在發生。有時,他們可以獨立行動,而在其他時候,他們可以在某個時候聚合以進行交互。例如,一輛汽車可以行駛而不會影響慢跑的人。它們是兩個獨立的事件流。但是,它們可能會在某個時間匯合,並且汽車在遇到慢跑者時將停止。

如果這是我們的世界運作的方式,爲什麼我們不這樣對代碼建模?爲什麼我們不將代碼建模爲同時發生的多個併發事件或數據流?對於開發人員來說,花費更多的時間來管理對象的狀態並以命令和順序的方式進行操作並不少見。您可以構造代碼以執行進程1,進程2,然後執行進程3,這取決於進程1和進程2。爲什麼不同時啓動進程1和進程2,然後完成這兩個事件就立即啓動進程3。 ?當然,您可以使用回調和Javaconcurrency工具,但是RxJava使表達起來更加容易和安全。

讓我們做最後一個觀察。一本書或音樂CD是靜態的。書是單詞的不變序列,而CD是曲目的集合。它們沒有動態關係,但是,當我們讀一本書時,我們一次讀一個單詞。那些話語被我們的眼睛吞沒了,有效地動了起來。對於音樂CD軌道而言,這沒有什麼不同,在CD軌道中,每個軌道都隨着聲波而運動,而您的耳朵正在消耗每個軌道。實際上,靜態項目也可以移動。這是一個抽象的但有力的想法,因爲我們使這些靜態項目中的每一個成爲一系列事件。當我們通過平等對待數據和事件之間的競爭環境時,我們釋放了功能編程的能力,並釋放了您以前可能認爲不切實際的功能。

響應式編程背後的基本思想是事件是數據,數據是事件,這看似抽象,但在考慮現實世界中的示例時並不需要很長時間。 跑步者和賽車都有屬性和狀態,但它們也在運動中。 書籍和CD消耗後會立即移動。 將事件和數據合併爲一體,可使代碼自然而有機地代表世界磨損模型。

爲什麼要學習RxJava?

ReactiveX和RxJava廣泛地解決了程序員日常面臨的許多問題,使您可以表達業務邏輯並減少工程代碼的時間。您是否曾經爲併發,事件處理,過時的數據狀態和異常恢復而苦苦掙扎?如何使您的代碼更具可維護性,可重用性和可擴展性,使其可以跟上您的業務發展呢?對於這些問題,調用響應式編程無處不在可能是冒昧的,但是在解決這些問題時,這無疑是一個進步。

用戶對使應用程序實時且響應迅速的需求也不斷增長。藉助反應式編程,您可以快速分析和處理實時數據源,例如Twitter提要或股票價格。它還可以取消和重定向工作,併發擴展,並處理快速發送的數據。將事件和數據組合爲可以混合,合併,過濾,拆分和轉換的流,爲組合和演化代碼提供了根本有效的方法。

總之,響應式編程使許多艱鉅的任務變得容易,使您能夠以您以前認爲不切實際的方式增加價值。如果您有一個響應式的進程,並且發現需要在不同的線程上運行它的一部分,則可以在幾秒鐘內實現此更改。如果您發現網絡連接問題間歇性地導致應用程序崩潰,則可以正常使用響應式恢復策略,然後再試一次。如果您需要在流程的中間插入操作,則就像插入新的運算符一樣簡單。響應式編程被分解成可以添加或刪除的模塊化鏈鏈接,這可以幫助快速克服所有上述問題。本質上,RxJava允許應用程序在保持生產穩定性的同時具有戰術性和可擴展性。

我們將在這本書中學到什麼?

如前所述,RxJava是Java的ReactiveX端口。在本書中,我們將主要關注RxJava 2.0,但我會指出RxJava 1.0的顯着差異。我們將把學習放在第一位,以進行反應性思考並利用RxJava的實用功能。從高層次的理解開始,我們將逐步深入瞭解RxJava的工作方式。在此過程中,我們將學習反應模式和技巧,以解決程序員遇到的常見問題。

在第2章“觀察和訂閱者”,第3章“基本運算符”和第4章“結合可觀察對象”中,我們將用Observable,Observer和Operator涵蓋Rx的核心概念。這是組成RxJava應用程序的三個核心實體。您將立即開始編寫響應式程序,並具有紮實的知識基礎,可以在本書的其餘部分繼續進行。

第5章,多播,重放和緩存,以及第6章,併發和並行化,將探討RxJava的更多細微差別以及如何有效利用併發性。

在第7章“切換,節流,窗口化和緩衝”以及第8章“可流動性和背壓”中,我們將學習應對反應性流的各種方法,這些響應流產生數據/事件的速度快於消耗。

最後,第9章,《變形和自定義運算符》,第10章,測試和調試,第11章,Android上的RxJava,以及第12章,將RxJava與Kotlin New結合使用,將涉及多個其他(但必不可少的)主題,包括自定義運算符以及如何使用。將RxJava與測試框架,Android和Kotlin語言結合使用。

配置

當前有兩種共存的RxJava版本:1.0和2.0。 稍後,我們將介紹一些主要差異,並討論您應該使用哪個版本。

RxJava 2.0是一個相當輕量級的庫,大小剛好超過2 MB,這使得它對於需要低依賴項開銷的Android和其他項目非常實用。 RxJava 2.0只有一個依賴項,稱爲Reactive Streams(http://www.reactive-streams.org/),它是一個核心庫(由RxJava的創建者製造),爲異步流實現設置了標準,其中之一是 RxJava 2.0。

它可以在RxJava之外的其他庫中使用,並且是Java平臺上的響應式編程標準化的關鍵工作。請注意,RxJava 1.0沒有任何依賴關係,包括Reactive Streams,這是在1.0之後實現的。

如果您是從頭開始的項目,請嘗試使用RxJava 2.0。這是我們將在本書中介紹的版本,但我會指出1.0版中的顯着差異。儘管由於使用RxJava 1.0的項目衆多,將長期支持RxJava 1.0,但創新可能只會在RxJava 2.0中繼續進行。 RxJava 1.0將僅獲得維護和錯誤修復。

RxJava 1.0和2.0都可以在Java 1.6上運行。在本書中,我們將使用Java 8,建議您至少使用Java 8,以便可以直接使用lambda。對於Android,有多種方法可以在較早的Java版本中利用lambda。但是自2014年以來,就一直在權衡Android Nougat使用Java 8和Java 8的事實,希望您不必採取任何變通辦法來利用lambda。

中央倉庫

要引入RxJava作爲依賴項,您有一些選擇。 最好的起點是轉到中央存儲庫(搜索http://search.maven.org/)並搜索rxjava。 您應該在搜索結果的頂部看到RxJava 2.0和RxJava 1.0作爲單獨的存儲庫,如以下屏幕截圖所示:
在這裏插入圖片描述
在撰寫本文時,RxJava 2.0.2是RxJava 2.0的最新版本,而RxJava 1.2.3是RxJava 1.0的最新版本。 您可以通過單擊下載列下面最右邊的JARlinks下載最新的JAR文件。 然後,您可以將項目配置爲使用JAR文件。

但是,您可能要考慮使用Gradle或Maven將這些庫自動導入到項目中。 這樣,您可以輕鬆地(通過GIT或其他版本控制系統)共享和存儲您的代碼項目,而不必每次都手動將RxJava下載和配置到該項目中。 要查看Maven,Gradle和其他幾個構建自動化系統的最新配置,請單擊任一存儲庫的版本號,如以下屏幕快照中所示:
在這裏插入圖片描述

使用Gradle

有幾種自動構建系統可用,但最主流的兩個選擇是Gradle和Maven。 Gradle在某種程度上是Maven的繼任者,尤其是針對Android開發的構建自動化解決方案。 如果您不熟悉Gradle並且想學習如何使用它,請查看Gradle入門指南(https://gradle.org/getting-started-gradle-java/)。

也有幾本體面的書以不同程度的深度介紹了Gradle,您可以在https://gradle.org/books/中找到它們。 以下屏幕快照顯示TheCentral Repository頁面,其中顯示瞭如何爲Gradle設置RxJava 2.0.2:
在這裏插入圖片描述
在build.gradle腳本中,確保已將mavenCentral()聲明爲您的存儲庫之一。 輸入或粘貼依賴項行 compile ‘io.reactivex.rxjava2:rxjava:x.y.z’,其中x.y.z是要使用的版本號,如以下代碼片段所示:

apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
      mavenCentral()
}
dependencies {      
   compile 'io.reactivex.rxjava2:rxjava:x.y.z'
}

構建您的Gradle項目,您應該一切順利! 然後,您將可以在項目中使用RxJava及其類型。

使用Maven

您還可以選擇使用Maven,並且可以通過選擇Apache Maven配置信息在中央存儲庫中查看適當的配置,如以下屏幕快照所示:

在這裏插入圖片描述

然後,您可以複製並粘貼包含RxJava配置的塊,並將其粘貼到pom.xml文件中的塊內。 重建您的項目,現在應該將RxJava設置爲依賴項。 x.y.z版本號與您要使用的所需RxJava版本相對應:

<project>  
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.nield</groupId>
    <artifactId>mavenrxtest</artifactId>
    <version>1.0</version>  
    <dependencies>    
       <dependency>
         <groupId>io.reactivex.rxjava2</groupId>     
         <artifactId>rxjava</artifactId>     
         <version>x.y.z</version>    
         </dependency>  
    </dependencies>
</project>

快速接觸RxJava

在我們深入研究RxJava的反應式世界之前,這裏有個快速入門,可以讓您先入爲主。在ReactiveX中,您將使用的核心類型是Observable。 在本書的其餘部分中,我們將學習更多有關Observable的知識。 但是本質上,一個Observable可以推動事物。 給定的Observable 通過一系列運算符推類型爲T的事物,直到它到達使用該項的觀察者爲止。

import io.reactivex.Observable;

public class Ch1_1 {
    public static void main(String[] args) {
        Observable<String> myStrings =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
    }
}

例如,在您的項目中創建一個新的Ch1_1.java文件,並輸入以下代碼:
在main()方法中,我們有一個Observable ,它將推送五個字符串對象。 一個Observable幾乎可以從任何來源推送數據或事件,無論是數據庫查詢還是實時Twitter提要。 在這種情況下,我們將使用Observable.just()快速創建一個Observable,它將發出一組固定的項。

在RxJava 2.0中,您將使用的大多數類型都包含在io.reactivex包中。 在RxJava1.0中,類型包含在rx包中。

但是,運行main()方法除了聲明Observable 之外不會做任何其他事情。 爲了使此Observable實際上推動這五個字符串(稱爲發射),我們需要一個Observer來訂閱它並接收項目。 我們可以通過傳遞一個lambda表達式來快速創建並連接一個Observer,該表達式指定如何處理接收到的每個字符串:

import io.reactivex.Observable;

public class Ch1_1 {
    public static void main(String[] args) {
        Observable<String> myStrings =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        myStrings.subscribe(s -> System.out.println(s));
    }
}

運行此代碼時,我們將獲得以下輸出:

在這裏插入圖片描述

這裏發生的是,我們的Observable 一次將每個字符串對象一次推送到我們的Observer,我們使用lambda表達式s-> System.out.println(s)來簡化它。我們通過參數s(我隨意命名)傳遞每個字符串,並指示它打印每個字符串。 Lambda本質上是mini函數,使我們能夠快速傳遞有關每個傳入項目要採取的操作的指令。箭頭左側的所有參數->是參數(在這種情況下,我們稱爲s的字符串),而右側的所有參數都是操作(是System.out.println(s))。

如果您不熟悉lambda表達式,請轉至附錄,以瞭解有關它們如何工作的更多信息。如果您想花更多的時間來理解lambda表達式,我強烈建議您至少閱讀Java 8 Lambdas(O’Reilly)的前幾​​章(http://shop.oreilly.com/product/0636920030713.do),作者是Richard Warburton。 。 Lambda表達式是現代編程中的一個關鍵主題,自從Java 8被Java開發人員使用以來,它就變得尤爲重要。我們將在本書中不斷使用lambdas,因此一定要花一些時間來熟悉它們。

我們還可以在Observable和Observer之間使用多個運算符來轉換每個推送的項目或以某種方式對其進行操作。每個運算符都返回一個新的Observable(繼承自上一個),但反映該轉換。例如,我們可以使用map()將每個字符串發射轉換爲它的length(),然後每個長度整數將被推送到Observer,如以下代碼片段所示:

import io.reactivex.Observable;

public class Ch1_2 {
    public static void main(String[] args) {
        Observable<String> myStrings =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        myStrings.map(s -> s.length()).subscribe(s ->
                System.out.println(s));
    }
}

運行此代碼時,我們將獲得以下輸出:

在這裏插入圖片描述

如果您使用過Java 8 Streams或Kotlin序列,您可能想知道Observable有何不同。 關鍵的區別是Observable會推送項目,而Streams和sequence會拉取項目,這似乎很微妙,但是基於push的迭代的影響要比基於pull的迭代強大得多。 不僅是數據,還有事件。 例如,Observable.interval()將在每個指定的時間間隔內推送一個連續的Long,如以下代碼片段所示。 Long發射不僅是數據,而且是事件! 讓我們來看看:

import io.reactivex.Observable;

import java.util.concurrent.TimeUnit;

public class Ch1_3 {
    public static void main(String[] args) {
        Observable<Long> secondIntervals =
                Observable.interval(1, TimeUnit.SECONDS);
        secondIntervals.subscribe(s -> System.out.println(s));
/* Hold main thread for 5 secondsso Observable above has chance to fire */
        sleep(5000);
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行此代碼時,我們將獲得以下輸出:

在這裏插入圖片描述

當您運行前面的代碼時,您將看到每秒發射一次連續發射。 該應用程序在退出之前將運行約五秒鐘,您可能會看到發射了0到4個排放物,每個排放物之間的間隔只有一秒鐘。 這個簡單的想法是數據是隨着時間流逝而發生的一系列事件,這將爲我們解決編程問題帶來新的可能性。

附帶說明一下,我們稍後會更多地涉及併發性,但是我們必須創建一個sleep()方法,因爲此Observable訂閱後會在計算線程上觸發發射。 用於啓動應用程序的主線程不會等待此Observable,因爲它是在計算線程而非主線程上觸發的。 因此,我們使用sleep()暫停主線程5000毫秒,然後允許它到達main()方法的末尾(這將導致應用程序終止)。 這使Observable.interval()有機會在應用程序退出之前觸發五秒鐘的窗口。

在整本書中,我們將發現許多有關可觀察的奧祕以及它爲我們服務的強大抽象。 如果您從概念上了解了到目前爲止發生的一切,那麼恭喜! 您已經熟悉了響應式代碼的工作方式。 再次強調一下,每次將排放一路推送到Observer.Emission代表數據和事件,可以隨時間推移而排放。 當然,除了map()之外,RxJava中還有數百個運算符,我們將在本書中學習關鍵點。 瞭解哪種操作員用於某種情況以及如何將其結合起來是掌握RxJava的關鍵。 在下一章中,我們將更全面地介紹Observable和Observer。 我們還將揭開Observable中所代表的事件和數據的神祕面紗。

RxJava 1.0與RxJava 2.0-我使用哪一個?

如前所述,建議您儘可能使用RxJava 2.0。它將繼續增長並獲得新功能,同時將維護RxJava 1.0進行錯誤修復。但是,還有其他一些考慮因素可能會導致您使用RxJava 1.0。如果您繼承了已經使用RxJava 1.0的項目,則可能會繼續使用該方法,直到將其重構爲2.0成爲可能。您還可以簽出David Akarnokd的RxJava2Interop項目(https://github.com/akarnokd/RxJava2Interop),該項目將Rx類型從RxJava 1.0轉換爲RxJava 2.0,反之亦然。讀完本書後,即使您擁有RxJava 1.0舊版代碼,也可以考慮使用該庫來利用RxJava 2.0。

在RxJava中,有幾個庫可以使多個Java API進行響應並無縫地插入RxJava。僅舉幾例,這些庫包括RxJava-JDBC,RxAndroid,RxJava-Extras,RxNetty和RxJavaFX。在撰寫本文時,只有RxAndroid和RxJavaFX已完全移植到RxJava 2.0(儘管以下許多其他庫)。到您閱讀本文時,所有主要的RxJava擴展庫都有望移植到RxJava 2.0。

您還將希望使用RxJava 2.0,因爲它是從RxJava 1.0獲得的許多後見之明和智慧之上構建的。它具有更好的性能,更簡單的API,更清潔的背壓方式,以及在與您自己的操作員一起砍伐時更安全。

何時使用RxJava

ReactiveX新手會問的一個常見問題是什麼情況下需要使用響應式方法?我們是否一直想使用RxJava?作爲從事實時響應式編程和呼吸一段時間的人,我瞭解到此問題有兩個答案:

第一個答案是您首次開始時:yes!您總是想採取被動的方法。真正成爲響應式編程大師的唯一方法是從頭開始構建響應式應用程序。將一切視爲可觀察的事物,並始終根據數據和事件流對程序進行建模。當您這樣做時,您將利用響應式編程所提供的一切,並看到您的應用程序質量顯着提高。

第二個答案是,當您熟悉RxJava時,會發現RxJava不合適的情況。有時響應式方法可能不是最佳方法,但是通常,此異常僅適用於部分代碼。您的整個項目本身應該是響應式的。可能有些部分沒有響應,這是有充分理由的。這些異常僅對訓練有素的Rx老手特別突出,他認爲返回List<String>可能比返回Observable <String>更好。

Rx使用者們不應該擔心什麼時候應該做出反應而是有些事情應該做出怎麼樣的反應。隨着時間的流逝,他們將開始看到將Rx的收益微不足道的情況,而這只是經驗帶來的。

因此,到目前爲止,沒有妥協。一路響應!

總結

在本章中,我們學習瞭如何以被動方式看待世界。作爲開發人員,您可能必須從傳統的命令式思維定型中重新訓練自己,並開發一個反應堆。尤其是如果您長時間進行了命令式,面向對象的編程,這可能會很困難。但是,隨着您的應用程序變得更加可維護,可擴展和優化,投資回報將非常可觀。您還將獲得更快的週轉時間和更清晰的代碼。

發佈了32 篇原創文章 · 獲贊 10 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章