第二章 Observables and Subscribers(观察者和订阅者)

第二章 Observables and Subscribers(观察者和订阅者)

导读

  在第1章“响应式思考”中,我们已经对Observable及其工作原理有所了解。 您可能对它的运作方式以及所拥有的实际应用有很多疑问。 本章将为理解可观察对象的工作方式以及与观察者之间的关键关系提供基础。 我们还将介绍几种创建Observable的方法,以及通过介绍几个运算符使它有用的方法。 为了使本书的其余部分顺利进行,我们还将直接涵盖所有关键的细微之处,以建立坚实的基础,以后再也不会让您感到惊讶。

  这是本章要介绍的内容:

  1、可观察的(Observable)

  2、观察者(Observer)

  3、其他可观察工厂

  4、单一,可完成和可能

  5、一次性的

一、Observable

  正如在第1章中所介绍的,Observable是基于推送的可组合迭代器。 对于给定的Observable ,它通过一系列运算符将typeT的项(称为发射)推送到最终到达最终的Observer,后者(Observer)是消耗项。 我们将介绍几种创建Observable的方法,但首先,让我们深入了解Observable如何通过其onNext(),onCompleted()和onError()调用工作。

1.1、Observable的工作原理

  在执行其他任何操作之前,我们需要研究一下Observable如何将链上的项目顺次传递到Observer。 在最高级别,一个Observable通过传递三种类型的事件来工作:

  onNext():这一次将每个项目从源Observable一直传递到Observer。

  onComplete():这将完成事件一直传递到Observer,指示不再发生onNext()调用。

  onError():这会将错误传递给Observer,观察者通常在其中定义如何处理它。 除非使用retry()运算符来拦截错误,否则Observable链通常会终止,并且不再发生发射。

  这三个事件是Observer类型中的抽象方法,稍后我们将介绍一些实现。 目前,我们将务实地关注它们在日常使用中的工作方式。

  在RxJava 1.0中,onComplete()事件实际上称为onCompleted()。

1.2、使用Observable.create()

  让我们从使用Observable.create()创建Observable开始。 相对而言,Observable源是发射在我们的可观察链的起点的可观察物。

  Observable.create()工厂允许我们通过提供接收Observable发射器的lambda来创建Observable。 我们可以调用Observable发射器的onNext()方法将发射(一次一次)传递到链上,也可以调用onComplete()来完成信号并传达将不再有其他项。 这些onNext()调用会将这些项目向上传递给Observer,它将在其中打印每个项目,如以下代码片段所示:

import io.reactivex.Observable;

public class Ch2_1 {
    public static void main(String[] args) {
        Observable<String> source = Observable.create(emitter -> {
            emitter.onNext("Alpha");
            emitter.onNext("Beta");
            emitter.onNext("Gamma");
            emitter.onNext("Delta");
            emitter.onNext("Epsilon");
            emitter.onComplete();
        });
        source.subscribe(s -> System.out.println("RECEIVED: " + s));
    }
}

  输出如下:

    RECEIVED: Alpha

    RECEIVED: Beta

    RECEIVED: Gamma

    RECEIVED: Delta

    RECEIVED: Epsilon

  在RxJava 1.0中,确保使用Observable.fromEmitter()而不是Observable.create()。 后者与RxJava 1.0完全不同,仅适用于高级RxJava用户。

  onNext()方法是一种将每个项目(从Alpha开始)传递到链中下一步的一种方法。 在此示例中,下一步是观察者,该观察者使用s -> System.out.println("RECEIVED: " + s) lambda打印项目。 该lambda在Observer的onNext()调用中调用,稍后我们将更仔细地观察Observer。

  onComplete()方法用于将链条向上传递给观察者,告知不再有物品要来。 Observable确实可以是无限的,如果是这种情况,将永远不会调用onComplete()事件。 从技术上讲,源可以停止发出onNext()调用,而从不调用onComplete()。 但是,如果源不再计划发射,则这可能是错误的设计。

  虽然这个特定的例子不太可能引发错误,但是我们可以捕获可能在Observable.create()块中发生的错误并通过onError()发出它们,这样就可以将错误推上链并由Observer处理。 我们设置的特定观察者不会处理异常,但是您可以这样做,如下所示:

import io.reactivex.Observable;

public class Ch2_2 {
    public static void main(String[] args) {
        Observable<String> source = Observable.create(emitter -> {
            try {
                emitter.onNext("Alpha");
                emitter.onNext("Beta");
                emitter.onNext("Gamma");
                emitter.onNext("Delta");
                emitter.onNext("Epsilon");
                emitter.onComplete();
            } catch (Throwable e) {
                emitter.onError(e);
            }
        });
        source.subscribe(s -> System.out.println("RECEIVED: " + s),
                Throwable::printStackTrace);
    }
}

  请注意,onNext(),onComplete()和onError()不一定直接推送到最终的Observer。 他们还可以推动operator(函数)充当链中的下一步。在以下代码中,我们使用map()和filter()运算符派生新的Observable,它们将在源Observable和最终Observer打印项目之间起作用:

import io.reactivex.Observable;

public class Ch2_3 {
    public static void main(String[] args) {
        Observable<String> source = Observable.create(emitter -> {
            try {
                emitter.onNext("Alpha");
                emitter.onNext("Beta");
                emitter.onNext("Gamma");
                emitter.onNext("Delta");
                emitter.onNext("Epsilon");
                emitter.onComplete();
            } catch (Throwable e) {
                emitter.onError(e);
            }
        });
        Observable<Integer> lengths = source.map(String::length);
        Observable<Integer> filtered = lengths.filter(i -> i >= 5);
        filtered.subscribe(s -> System.out.println("RECEIVED: " +
                s));
    }
}

  这是运行代码后的输出:

    RECEIVED: 5
    RECEIVED: 5
    RECEIVED: 5
    RECEIVED: 7

  通过在源Observable和Observer之间使用map()和filter()运算符,onNext()将把每个项目交给map()运算符。在内部,它将充当中间观察者,并将每个字符串转换为它的length()。反过来,这将调用filter()上的onNext()以传递该整数,并且lambda条件i-> i> = 5将抑制长度至少为五个字符的发射。最后,filter()运算符将调用onNext()将每一项移交给最终的观察者,在此处将其打印出来。

  重要的是要注意,map()运算符将产生一个派生自原始Observable<String>的新Observable<Integer>。 filter()也将返回Observable<Integer>但忽略不符合标准的发射,由于map()和filter()等运算符产生新的Observables(内部使用Observer实现接收发射),因此我们可以将所有链使用下一个运算符返回Observable,而不是将每个变量不必要地保存到中间变量中:

import io.reactivex.Observable;

public class Ch2_4 {
    public static void main(String[] args) {
        Observable<String> source = Observable.create(emitter -> {
            try {
                emitter.onNext("Alpha");
                emitter.onNext("Beta");
                emitter.onNext("Gamma");
                emitter.onNext("Delta");
                emitter.onNext("Epsilon");
                emitter.onComplete();
            } catch (Throwable e) {
                emitter.onError(e);
            }
        });
        source.map(String::length)
                .filter(i -> i >= 5)
                .subscribe(s -> System.out.println("RECEIVED: " + s));
    }
}

  这是运行代码后的输出:

    RECEIVED: 5
    RECEIVED: 5
    RECEIVED: 5
    RECEIVED: 7

  在响应式式编程中,以这种方式链接操作符是常见的(并鼓励使用)。 它具有很好的可读性,从左到右,从上到下都像一本书,这有助于维护性和可读性。

  在RxJava 2.0中,Observables不再支持发射空值。 如果创建试图发出null值的Observable,则将立即获得非null异常。 如果需要发出null,请考虑将其包装在Java 8或Google Guava Optional中。

1.3、使用Observable.just()

  在我们进一步讨论subscribe()方法之前,请注意,您可能不需要经常使用Observable.create()。 它有助于连接某些非响应式的资源,本章稍后将在几个地方看到这一点。 但通常情况下,我们使用简化的工厂为常见资源创建Observable。

  在上一个带有Observable.create()的示例中,我们可以使用Observable.just()完成此操作。 我们最多可以传递10个要发送的商品。 它将为每个调用onNext()调用,然后在所有这些都被推送时调用onComplete():

import io.reactivex.Observable;

public class Ch2_5 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        source.map(String::length).filter(i -> i >= 5)
                .subscribe(s -> System.out.println("RECEIVED: " + s));
    }
}

  我们还可以使用Observable.fromIterable()从任何Iterable类型(例如List)发出项目。 它还将为每个元素调用onNext(),然后在迭代完成后调用onComplete()。 您可能会经常使用此工厂,因为Java中的Iterables很常见,并且可以很容易地使其具有响应性:

import io.reactivex.Observable;

import java.util.Arrays;
import java.util.List;

public class Ch2_6 {
    public static void main(String[] args) {
        List<String> items =
                Arrays.asList("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        Observable<String> source = Observable.fromIterable(items);
        source.map(String::length).filter(i -> i >= 5)
                .subscribe(s -> System.out.println("RECEIVED: " + s));
    }
}

  在本章的后面,我们将探索其他工厂来创建Observable,但是现在让我们搁置它,了解有关Observers的更多信息。

二、Observer接口

  onNext(),onComplete()和onError()方法实际上定义了Observer类型,它是在RxJava中实现的用于传达这些事件的抽象接口。 这是代码片段中显示的RxJava中的Observer定义。 现在不要为onSubscribe()烦恼,因为我们将在本章结尾处介绍它。 只需引起您对其他三种方法的注意:

public interface Observer<T> {
    void onSubscribe(@NonNull Disposable var1);

    void onNext(@NonNull T var1);

    void onError(@NonNull Throwable var1);

    void onComplete();
}

  观察者和观察来源是相对的。在一种情况下,“可观察的”源是您的“可观察的链”开始的地方和发射的来源。在前面的示例中,您可以说从Observable.create()方法或Observable.just()返回的Observable是源Observable。但是对于filter()运算符,从map()运算符返回的Observable是源。它不知道发射物来自何处,只是知道它正在接收来自其上游的操作员的发射物,这些发射物来自map()。

  相反,方法函数返回的每个Observable内部都是一个Observer,它接收,转换和发射到下一个下游Observer。它不知道下一个观察者是链末尾的另一个函数还是最终的观察者。当我们谈论观察者时,我们经常谈论的是消耗发射的可观察链末端的最终观察者。但是每个运算符,例如map()和filter(),也在内部实现Observer。

  我们将在第9章``变形和自定义运算符’'中详细了解运算符的构建方式。目前,我们将重点放在对subscribe()方法使用Observer上。

  在RxJava 1.0中,订阅者实质上成为RxJava 2.0中的观察者。 RxJava 1.0中有一个Observer类型,它定义了三个事件方法,但是Subscriber是您传递给subscribe()方法的对象,并且实现了Observer。 在RxJava 2.0中,仅在谈论Flowable时存在订户,我们将在第8章Flowables和Backpressure中进行讨论。

2.1、实现和订阅观察者

  当您在Observable上调用subscription()方法时,Observer通过实现其方法来使用这三个事件。 可以像之前那样指定lambdaarguments,而不是实现Observer并将其实例传递给subscription()方法。 暂时不要为onSubscribe()烦恼。 只需将其实现留空,直到我们在本章末尾讨论它时:

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;

public class Ch2_7 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        Observer<Integer> myObserver = new Observer<Integer>() {
            @Override
            public void onSubscribe(Disposable d) {
                //与Disposable不做任何事,暂时不考虑
            }

            @Override
            public void onNext(Integer value) {
                System.out.println("RECEIVED: " + value);
            }

            @Override
            public void onError(Throwable e) {
                e.printStackTrace();
            }

            @Override
            public void onComplete() {
                System.out.println("Done!");
            }
        };
        source.map(String::length).filter(i -> i >= 5)
                .subscribe(myObserver);
    }
}

  输出如下:

    RECEIVED: 5
    RECEIVED: 5
    RECEIVED: 5
    RECEIVED: 7
    Done!

  我们快速创建一个Observer<Integer>作为我们的Observer,它将接收整数长度的发射。我们的观察员在可观察链的末尾接收发射,并充当消耗发射的终点。通过消耗,这意味着它们到达了过程的末尾,在那里它们被写入数据库,文本文件,服务器响应,在UI中显示,或者(在这种情况下)仅打印到控制台。

  为了进一步详细说明此示例,我们从源头的字符串发射开始。我们预先声明了Observer并将其传递给Observable链末尾的subscribe()方法。请注意,每个字符串都将转换为其长度。 onNext()方法接收每个整数长度的发射,并使用System.out.println(“ RECEIVED:” value)进行打印。我们不会在运行此简单过程时遇到任何错误,但是如果在Observable链中的任何位置确实发生了错误,它将被推送到Observer的onError()实现中,该处将打印Throwable的堆栈跟踪。最后,当源没有更多的发射时(按“ Epsilon”后),它将一直沿链向上调用onComplete()到Observer,在此将调用其onComplete()方法并打印 Done! 到控制台。

2.2、lambda的速记观察者

实现观察者有点冗长和繁琐。 幸运的是,subscribe()方法已重载为我们的三个事件接受了lambda参数。 这可能是我们在大多数情况下要使用的参数,我们可以指定三个用逗号分隔的lambda参数:onNext lambda,onError lambda和onComplete lambda。 对于前面的示例,我们可以使用这三个lambda合并我们的三个方法实现:

Consumer<Integer> onNext = i ->  System.out.println("RECEIVED: "+ i);    
Action onComplete = () -> System.out.println("Done!");    
Consumer<Throwable> onError = Throwable::printStackTrace;

  我们可以将这三个lambda作为参数传递给subscribe()方法,它将使用它们来为我们实现一个Observer。 这更加简洁,所需模板代码更少:

import io.reactivex.Observable;

public class Ch2_8 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        source.map(String::length).filter(i -> i >= 5)
                .subscribe(i -> System.out.println("RECEIVED: " + i),
                        Throwable::printStackTrace,
                        () -> System.out.println("Done!"));
    }
}

  请注意,subscribe()还有其他重载。 您可以省略onComplete(),而仅实现onNext()和onError()。 这将不再对onComplete()执行任何操作,但是在某些情况下,您可能不需要以下操作:

import io.reactivex.Observable;

public class Ch2_9 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        source.map(String::length).filter(i -> i >= 5)
                .subscribe(i -> System.out.println("RECEIVED: " + i),
                        Throwable::printStackTrace);
    }
}

  正如您在前面的示例中看到的那样,您甚至可以省略onError,而只需指定onNext即可:

import io.reactivex.Observable;

public class Ch2_10 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta",
                        "Epsilon");
        source.map(String::length).filter(i -> i >= 5)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
    }
}

  但是,您要避免在生产中避免实现onError()。 发生在Observable链中任何地方的错误将传播到onError()进行处理,然后终止Observable且不再发出任何错误。 如果您没有为onError指定操作,则将解决该错误。

您可以使用retry()运算符尝试恢复并在发生错误时重新预订Observable。 我们将在下一章介绍如何做到这一点。

重要的是要注意,大多数subscribe()重载变量(包括我们刚刚覆盖的速记lambda变量)都返回一个我们没有做任何事情的Disposable。 Disposable使我们能够将观察物与观察者断开连接,从而尽早终止发射,这对于无限或长期运行的观察物至关重要。 我们将在本章结尾介绍Disposable。

三、冷观察与热观察

  根据Observable的实现方式,Observable和Observer之间的关系中存在细微的行为。需要注意的是冷观察者与热观察者的主要特征,它定义了当存在多个观察者时观察者的行为。首先,我们将介绍冷的Observable。

3.1、冷观察

  Cold Observables非常类似于可以向每个听众播放的音乐CD,因此每个人都可以随时收听所有曲目。以相同的方式,冷的Observables将向每个观察者(Observer)重新发射,确保所有观察者都获得所有数据。大多数数据驱动的Observable都是冷的,其中包括Observable.just()和Observable.fromIterable()工厂。在下面的示例中,我们有两个Observer订阅了一个Observable。 Observable将首先向第一个Observer的发射所有发射,然后调用onComplete()。然后,它将再次向第二个Observer播放所有发射,并调用onComplete()。它们都通过获取两个单独的流来接收相同的数据集,这是冷Observable的典型行为:

import io.reactivex.Observable;

public class Ch2_11 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        //first observer
        source.subscribe(s -> System.out.println("Observer 1 Received: " + s));
        //second observer
        source.subscribe(s -> System.out.println("Observer 2 Received: " + s));
    }
}

  输出如下:

    Observer 1 Received: Alpha
    Observer 1 Received: Beta
    Observer 1 Received: Gamma
    Observer 1 Received: Delta
    Observer 1 Received: Epsilon
    Observer 2 Received: Alpha
    Observer 2 Received: Beta
    Observer 2 Received: Gamma
    Observer 2 Received: Delta
    Observer 2 Received: Epsilon

  即使第二个观察者通过方法函数改变其发射量,它仍将获得自己的发射量。 对冷的Observable使用诸如map()和filter()之类的运算符仍将保持所产生的Observable的冷性:

import io.reactivex.Observable;

public class Ch2_12 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
//first observer
        source.subscribe(s -> System.out.println("Observer 1 Received: " + s));
//second observer
        source.map(String::length).filter(i -> i >= 5)
                .subscribe(s -> System.out.println("Observer 2 Received: " + s));
    }
}

  输出如下:

    Observer 1 Received: Alpha
    Observer 1 Received: Beta
    Observer 1 Received: Gamma
    Observer 1 Received: Delta
    Observer 1 Received: Epsilon
    Observer 2 Received: 5
    Observer 2 Received: 5
    Observer 2 Received: 5
    Observer 2 Received: 7

  如前所述,发出有限数据集的可观察源通常是冷的。

3.2、热点观察

  您刚刚了解了冷的Observable,其工作原理类似于音乐CD。热的Observable更像是电台。它同时向所有Observer广播相同的发射。如果观察者Observer订阅了热的Observable,收到了一些发射量,然后又有另一个观察者进来,则该第二观察者将错过这些发射量。就像广播电台一样,如果调得太晚,您会错过那首歌。从逻辑上讲,热的Observable通常表示事件,而不是有限的数据集。这些事件可以携带数据,但是有一个时间敏感的组件,后期的观察者可能会错过以前发出的数据,例如,JavaFX或Android UI事件可以表示为热门的Observable。在JavaFX中,可以使用Observable.create()在ToggleButton的selectedProperty()运算符旁边创建Observable 。然后,您可以将布尔发射转换为指示ToggleButton是UP还是DOWN的字符串,然后使用Observer在Label中显示它们,如以下代码片段所示:

import io.reactivex.Observable;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Ch2_13 extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        ToggleButton toggleButton = new ToggleButton("TOGGLE ME");
        Label label = new Label();
        Observable<Boolean> selectedStates =
                valuesOf(toggleButton.selectedProperty());
        selectedStates.map(selected -> selected ? "DOWN" : "UP")
                .subscribe(label::setText);
        VBox vBox = new VBox(toggleButton, label);
        stage.setScene(new Scene(vBox));
        stage.show();
    }

    private static <T> Observable<T> valuesOf(final
                                              ObservableValue<T> fxObservable) {
        return Observable.create(observableEmitter -> {
            //emit initial state
            observableEmitter.onNext(fxObservable.getValue());
            //emit value changes uses a listener
            final ChangeListener<T> listener = (observableValue, prev,
                                                current) -> observableEmitter.onNext(current);
            fxObservable.addListener(listener);
        });
    }
}

  JavaFX ObservableValue与RxJava Observable无关。它是JavaFX的专有属性,但是我们可以使用先前实现的valuesOf()工厂轻松地将其转换为RxJava Observable,以将ChangeListener挂钩为onNext()调用。每次单击ToggleButton时,Observable 将发出true或false反映选择状态。这是一个简单的示例,表明此Observable发出事件,但也发出true或false形式的数据。它将布尔值转换为字符串,并让观察者修改Label的文本。

  在此JavaFX示例中,我们只有一个观察者。如果我们要在发射发生后为该ToggleButton的事件带来更多观察员,则那些新的观察员将错过这些发射。

  JavaFX和Android上的UI事件是热的Observable的主要示例,但是您也可以使用热的Observable来反映服务器请求。如果您在实时Twitter流上创建了一个Observable,该Observable发出了某个主题的推文,那也将是一个热门的Observable。所有这些来源可能都是无限的,尽管许多热门的Observables确实是无限的,但它们不一定是无限的。他们只需要同时向所有观察员共享发射,而不必为迟到的观察员重播错过的发射。

  请注意,由于我们从未处理过该JavaFX示例,因此确实留下了一个宽松的结局。在本章末尾介绍Disposables时,我们将再次进行讨论。

3.3、ConnectableObservable

热的Observable一种有用形式是ConnectableObservable。 即使是冷的,它也会采取任何可观察的方法,并使其变热,以便所有发射立即播放给所有观察者。 要进行此转换,您只需要在任何Observable上调用publish(),它将产生一个ConnectableObservable。 但是订阅还不会开始发射。 您需要调用其connect()方法来开始发射。 这使您可以预先设置所有观察者。 看一下以下代码片段:

import io.reactivex.Observable;
import io.reactivex.observables.ConnectableObservable;

public class Ch2_14 {
    public static void main(String[] args) {
        ConnectableObservable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                        .publish();
        //Set up observer 1
        source.subscribe(s -> System.out.println("Observer 1: " + s));
        //Set up observer 2
        source.map(String::length)
                .subscribe(i -> System.out.println("Observer 2: " + i));
        //Fire!
        source.connect();
    }
}

  输出如下:

  Observer 1: Alpha
  Observer 2: 5
  Observer 1: Beta
  Observer 2: 4
  Observer 1: Gamma
  Observer 2: 5
  Observer 1: Delta
  Observer 2: 5
  Observer 1: Epsilon
  Observer 2: 7

  请注意,一个观察者是如何接收字符串的,而另一个观察者是如何接收长度的,而两个观察者是如何以交错方式打印它们的。 预先设置了两个订阅,然后调用connect()触发发射。 而不是由观察者1在观察者2之前处理所有发射,而是将每个发射同时转到每个观察者。 观察者1接收Alpha,观察者2接收5,然后Beta和4,依此类推。 使用ConnectableObservable强制将每个发射同时发送到所有观察者称为多播,我们将在第5章“多播”中详细介绍。

  ConnectableObservable有助于防止将数据重放到每个Observer。 如果重放发射成本很高,而您宁愿立即将它们发射给所有观察员,则可能要这样做。 您也可以这样做,即使下游有多个观察员,也可以强制上游操作员使用单个流实例。 多个观察者通常会导致上游有多个流实例,但是使用publish()返回ConnectableObservable会在publish()之前将所有上游操作合并为一个流。 同样,这些细微差别将在第5章“多播”中进行详细介绍。现在,请记住ConnectableObservable很热,因此,如果在connect()调用之后发生新的订阅,它们将错过以前触发的发射。

四、其他Observable来源

  我们已经介绍了一些工厂来创建Observable源,包括Observable.create(),Observable.just()和Observable.fromIterable()。 在绕过观察者及其细微差别的绕道绕道之后,让我们从中断的地方开始,并介绍一些Observable工厂。

4.1、Observable.range()

  要发出连续范围的整数,可以使用Observable.range()。 这将从起始值发出每个数字,并递增每次发出直到达到指定的计数。 这些数字都通过onNext()事件传递,然后通过onComplete()事件传递:

import io.reactivex.Observable;

public class Ch2_15 {
    public static void main(String[] args) {
        Observable.range(1, 10)
                .subscribe(s -> System.out.println("RECEIVED: " + s));
    }
}

  输出如下:

    RECEIVED: 1
    RECEIVED: 2
    RECEIVED: 3
    RECEIVED: 4
    RECEIVED: 5
    RECEIVED: 6
    RECEIVED: 7
    RECEIVED: 8
    RECEIVED: 9
    RECEIVED: 10

请密切注意,Observable.range()的两个参数不是下限/上限。 第一个参数是起始值。 第二个参数是发射总量,其中将包括初始值和增量值。

  尝试发出Observable.range(5,10),您会注意到它发出5,然后是下一个连续的9个连续整数(总共发出10个):

import io.reactivex.Observable;

public class Ch2_16 {
    public static void main(String[] args) {
        Observable.range(5, 10)
                .subscribe(s -> System.out.println("RECEIVED: " + s));
    }
}

  输出如下:

    RECEIVED: 5

    RECEIVED: 6

    RECEIVED: 7

    RECEIVED: 8

    RECEIVED: 9

    RECEIVED: 10

    RECEIVED: 11

    RECEIVED: 12

    RECEIVED: 13

    RECEIVED: 14

请注意,如果需要发出更大的数字,则还有一个长等效项,称为Observable.rangeLong()。

4.2、Observable.interval()

  如我们所见,可观测对象具有随着时间推移而发射的概念。 发射从源头开始依次传递给观察者。 但是这些发射物可以按时间间隔分配,具体取决于发射源何时提供。 我们的带有ToggleButton的JavaFX示例演示了这一点,因为每次单击都会产生对或错。

  但是,让我们来看一个使用Observable.interval()的基于时间的Observable的简单示例。 它将在每个指定的时间间隔发出连续的长发射(从0开始)。 在这里,我们有一个Observable<Long>,它每秒发出一次:

import io.reactivex.Observable;

import java.util.concurrent.TimeUnit;

public class Ch2_17 {
    public static void main(String[] args) {
        Observable.interval(1, TimeUnit.SECONDS)
                .subscribe(s -> System.out.println(s + " Mississippi"));
        sleep(5000);
    }

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

Observable.interval()将以指定的间隔(在这种情况下为1秒)无限发射。 但是,由于它在计时器上运行,因此它需要在单独的线程上运行,并且默认情况下将在计算调度程序上运行。

  我们将在第6章“并发性和并行化”中介绍并发性,并了解调度程序。 现在,仅需注意我们的main()方法将启动此Observable,但它不会等待其完成。 现在它在单独的线程上发出。 为了防止我们的main()方法在Observable有机会触发之前完成并退出应用程序,我们使用sleep()方法使该应用程序保持活动状态五秒钟。 在应用程序退出之前,这将使我们的Observable发出五秒钟的时间。 创建生产应用程序时,您可能不会经常遇到此问题,因为用于Web服务,Android应用程序或JavaFX等任务的非守护进程线程会使该应用程序保持活动状态。

技巧问题:Observable.interval()返回的是热还是冷Observable?由于它是事件驱动的(并且是无限的),因此您可能会倾向于说它是热的。 但是在上面放一个观察者,等待五秒钟,然后添加另一个观察者。 会怎么样呢? 让我们来看看:

import io.reactivex.Observable;

import java.util.concurrent.TimeUnit;

public class Ch2_18 {
    public static void main(String[] args) {
        Observable<Long> seconds = Observable.interval(1,
                TimeUnit.SECONDS);
        //Observer 1
        seconds.subscribe(l -> System.out.println("Observer 1: " + l));
        //sleep 5 seconds
        sleep(5000);
        //Observer 2
        seconds.subscribe(l -> System.out.println("Observer 2: " + l));
        //sleep 5 seconds
        sleep(5000);
    }

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

  输出如下:

    Observer 1: 0
    Observer 1: 1
    Observer 1: 2
    Observer 1: 3
    Observer 1: 4
    Observer 1: 5
    Observer 2: 0
    Observer 1: 6
    Observer 2: 1
    Observer 1: 7
    Observer 2: 2
    Observer 1: 8
    Observer 2: 3
    Observer 1: 9
    Observer 2: 4

  看一下观察者2进入五秒钟后发生了什么。请注意,它在自己的单独计时器上,从0开始! 这两个观察者实际上正在获得自己的发射,每个发射都从0开始。因此,这个Observable实际上是冷的。

  要将所有观察者置于具有相同发射率的同一计时器上,您将需要使用ConnectableObservable强制使这些发射变热:

import io.reactivex.Observable;
import io.reactivex.observables.ConnectableObservable;

import java.util.concurrent.TimeUnit;

public class Ch2_19 {
    public static void main(String[] args) {
        ConnectableObservable<Long> seconds =
                Observable.interval(1, TimeUnit.SECONDS).publish();
        //observer 1
        seconds.subscribe(l -> System.out.println("Observer 1: " + l));
        seconds.connect();
        //sleep 5 seconds
        sleep(5000);
        //observer 2
        seconds.subscribe(l -> System.out.println("Observer 2: " + l));
        //sleep 5 seconds
        sleep(5000);
    }

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

  输出如下:

  Observer 1: 0
  Observer 1: 1
  Observer 1: 2
  Observer 1: 3
  Observer 1: 4
  Observer 1: 5
  Observer 2: 5
  Observer 1: 6
  Observer 2: 6
  Observer 1: 7
  Observer 2: 7
  Observer 1: 8
  Observer 2: 8
  Observer 1: 9
  Observer 2: 9

4.3、Observable.future()

  RxJava Observables比Futures更健壮和更具表现力,但是如果您已有产生Futures的库,则可以通过Observable.future()轻松将它们转换为Observables:

4.4、Observable.empty()

  尽管这似乎似乎没有用,但有时创建不发出任何内容并调用onComplete()的Observable有时会有所帮助:

import io.reactivex.Observable;

public class Ch2_20 {
    public static void main(String[] args) {
        Observable<String> empty = Observable.empty();
        empty.subscribe(System.out::println,
                Throwable::printStackTrace,
                () -> System.out.println("Done!"));
    }
}

  请注意,没有发射内容被打印,因为没有发射。 它直接调用onComplete,它打印 Done! 观察者中的消息。 空的可观察值通常表示空的数据集。 当所有发射均不满足条件时,它们也可能由诸如filter()之类的运算符产生。 有时,您会使用Observable.empty()故意创建空的Observable,而在本书中的某些地方我们都会看到一些示例。

  空的Observable本质上是RxJava的null概念。 这是缺少值(从技术上讲,是“值”)。 Empty Observables比null更优雅,因为操作将简单地继续为空而不是抛出NullPointerExceptions。 但是,当RxJava程序出现问题时,有时是因为观察者没有收到任何辐射。 发生这种情况时,您必须跟踪可观察对象的操作员链,以找出导致发射变为空的原因。

4.5、Observable.never()

  Observable.never()很像Observable.empty()。 它们之间的唯一区别是,它从不调用onComplete(),永远让观察者等待发射,但从不实际提供任何东西:

import io.reactivex.Observable;

public class Ch2_21 {
    public static void main(String[] args) {
        Observable<String> never = Observable.never();
        never.subscribe(System.out::println,
                Throwable::printStackTrace,
                () -> System.out.println("Done!"));
        sleep(5000);
    }

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

  此Observable主要用于测试,在生产中并不经常使用。 我们必须像Observable.interval()一样在这里使用sleep(),因为在启动之后主线程不会等待它。 在这种情况下,我们只需使用sleep()五秒钟即可证明没有任何发射。 然后,该应用程序将退出。

4.6、Observable.error()

  这也可能只是测试而已,但是您可以创建一个Observable,该对象立即使用指定的异常调用onError()

import io.reactivex.Observable;

public class Ch2_22 {
    public static void main(String[] args) {
        Observable.error(new Exception("Crash and burn!"))
                .subscribe(i -> System.out.println("RECEIVED: " + i),
                        Throwable::printStackTrace,
                        () -> System.out.println("Done!"));
    }
}
4.7、Observable.defer()

  Observable.defer()是强大的工厂,因为它能够为每个Observer创建单独的状态。 使用某些可观察的工厂时,如果源是有状态的,并且要为每个观察者创建一个单独的状态,则可能会遇到一些细微差别。 您的源Observable可能无法捕获其参数发生变化的某些内容并发送过时的发射。 这是一个简单的示例:我们有一个Observable.range(),它建立在两个静态int属性上,即start和count。

  如果您订阅此Observable,请修改计数,然后再次订阅,您将发现第二个Observer没有看到此更改:

import io.reactivex.Observable;

public class Ch2_24 {
    private static int start = 1;
    private static int count = 5;

    public static void main(String[] args) {
        Observable<Integer> source = Observable.range(start, count);
        source.subscribe(i -> System.out.println("Observer 1: " + i));
        //modify count
        count = 10;
        source.subscribe(i -> System.out.println("Observer 2: " + i));
    }
}

  输出如下:

    Observer 1: 1
    Observer 1: 2
    Observer 1: 3
    Observer 1: 4
    Observer 1: 5
    Observer 2: 1
    Observer 2: 2
    Observer 2: 3
    Observer 2: 4
    Observer 2: 5

  要解决可观察源无法捕获状态更改的问题,可以为每个订阅创建一个新的可观察对象。 这可以使用Observable.defer()实现,该函数接受一个lambda指令,该lambda指示如何为每个订阅创建一个Observable。 因为这每次都会创建一个新的Observable,所以它将反映驱动其参数的所有更改:

import io.reactivex.Observable;

class Ch2_25 {
    private static int start = 1;
    private static int count = 5;

    public static void main(String[] args) {
        Observable<Integer> source = Observable.defer(() ->
                Observable.range(start, count));
        source.subscribe(i -> System.out.println("Observer 1: " + i));
        //modify count
        count = 10;
        source.subscribe(i -> System.out.println("Observer 2: " + i));
    }
}

  输出如下:

    Observer 1: 1
    Observer 1: 2
    Observer 1: 3
    Observer 1: 4
    Observer 1: 5
    Observer 2: 1
    Observer 2: 2
    Observer 2: 3
    Observer 2: 4
    Observer 2: 5
    Observer 2: 6
    Observer 2: 7
    Observer 2: 8
    Observer 2: 9
    Observer 2: 10

  这是很好的! 当您的Observable源未捕获对驱动它的事物的更改时,请尝试将其放入Observable.defer()中。 如果您的Observable源代码是天真的实现的,并且在多个Observer上表现不佳(例如,它重用了仅迭代一次数据的anIterator),那么Observable.defer()也为此提供了一种快速的解决方法。

4.8、Observable.fromCallable()

  如果需要执行计算或操作然后发出它,则可以使用Observable.just()(或Single.just()或Maybe.just(),我们将在后面介绍)。 但有时,我们希望以懒惰或推迟的方式执行此操作。 另外,如果该过程引发错误,我们希望通过onError()在Observable链中发出该错误,而不是以传统的Java方式在该位置将该错误引发。 例如,如果您尝试将Observable.just()包裹在一个被1除以0的表达式周围,则会抛出异常,而不是向Observer发出该异常:

import io.reactivex.Observable;

public class Ch2_26 {
    public static void main(String[] args) {
        Observable.just(1 / 0)
                .subscribe(i -> System.out.println("RECEIVED: " + i),
                        e -> System.out.println("Error Captured: " + e));
    }
}

  输出如下:

    Exception in thread “main” java.lang.ArithmeticException: / by zero

  如果我们要对错误处理做出反应,那么这可能是不理想的。 也许您希望将错误沿着链条向下发送到观察者,并在其中进行处理。 如果是这种情况,请改用Observable.fromCallable(),因为它接受lambda Supplier ,并且将发出直到Observer发生的任何错误:

import io.reactivex.Observable;

public class Ch2_27 {
    public static void main(String[] args) {
        Observable.fromCallable(() -> 1 / 0)
                .subscribe(i -> System.out.println("Received: " + i),
                        e -> System.out.println("Error Captured: " + e));
    }
}

  输出如下:

    Error Captured: java.lang.ArithmeticException: / by zero

  这是很好的! 错误被发送给观察者,而不是被抛出。 如果初始化发射有可能引发错误,则应使用Observable.fromCallable()而不是Observable.just()。

五、Single,Completable和Maybe

  有一些专门针对Observable的风格,它们是为一个或一个发射明确设置的:Single,Maybe和Completable。 这些都紧密遵循Observable,并且应该直观地用于您的反应式编码工作流中。 您可以像创建Observable一样创建它们(例如,它们每个都有自己的create()工厂),但是某些Observable运算符也可能返回它们。

5.1、Single

  Single<T>本质上是一个Observable<T>,只会发出一项。 它的工作方式类似于Observable,但仅限于对单个发射有意义的运算符。 它还具有自己的SingleObserver接口:

public interface SingleObserver<T> {
    void onSubscribe(@NonNull Disposable var1);

    void onSuccess(@NonNull T var1);

    void onError(@NonNull Throwable var1);
}

  onSuccess()本质上将onNext()和onComplete()合并为一个事件,该事件接受一个发射。 当您针对Single调用subscribe()时,您需要为onSuccess()和可选的onError()提供lambda:

import io.reactivex.Single;

public class Ch2_28 {
    public static void main(String[] args) {
        Single.just("Hello")
                .map(String::length)
                .subscribe(System.out::println,
                        Throwable::printStackTrace);
    }
}

  某些RxJava Observable运算符将产生Single,我们将在下一章中看到。例如,first()运算符将返回Single,因为该运算符在逻辑上涉及单个项目。 但是,如果Observable输出为空,它将接受默认值作为参数(在以下示例中将其指定为Nil):

import io.reactivex.Observable;

public class Ch2_29 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma");
        source.first("Nil") //returns a Single
                .subscribe(System.out::println);
    }
}

  输出如下:Alpha

  Single必须有一个发射,如果您只提供一个发射,则应该选择它。 这意味着您应该尝试使用Single.just(“ Alpha”)而不是Observable.just(“ Alpha”)。 Single上有一些运算符,您可以在需要时将其转换为Observable,例如toObservable()。

  如果发射为0或1,则需要使用Maybe。

5.2、Maybe

  Maybe就像是Single一样,除了它根本不允许发射。MaybeObserver很像标准的Observer,但是onNext()被称为onSuccess():

public interface MaybeObserver<T> {
    void onSubscribe(@NonNull Disposable var1);

    void onSuccess(@NonNull T var1);

    void onError(@NonNull Throwable var1);

    void onComplete();
}

  给定的Maybe 将仅发射0或1个发射。 它将可能的发射传递给onSuccess(),无论哪种情况,完成后都会调用onComplete()。 Maybe.just()可用于创建可能发射单个项目的Maybe。 Maybe.empty()将创建不产生任何发射的Maybe:

import io.reactivex.Maybe;

public class Ch2_30 {
    public static void main(String[] args) {
        // has emission
        Maybe<Integer> presentSource = Maybe.just(100);
        presentSource.subscribe(s -> System.out.println("Process 1 received:" + s),
                Throwable::printStackTrace,
                () -> System.out.println("Process 1 done!"));
        //no emission
        Maybe<Integer> emptySource = Maybe.empty();
        emptySource.subscribe(s -> System.out.println("Process 2 received:" + s),
                Throwable::printStackTrace,
                () -> System.out.println("Process 2 done!"));
    }
}

  输出如下:

    Process 1 received: 100
    Process 2 done!

  我们稍后将学习的某些Observable运算符产生的Maybe。 一个示例是firstElement()运算符,它与first()相似,但是如果没有发出任何元素,它将返回空结果:

import io.reactivex.Observable;

public class Ch2_31 {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        source.firstElement().subscribe(
                s -> System.out.println("RECEIVED " + s),
                Throwable::printStackTrace,
                () -> System.out.println("Done!"));
    }
}

  输出如下:RECEIVED Alpha

5.3、Completable

  Completable仅与正在执行的动作有关,但不接收任何发射。 从逻辑上讲,它没有onNext()或onSuccess()来接收发射,但是它确实具有onError()和onComplete():

public interface CompletableObserver {
    void onSubscribe(@NonNull Disposable var1);

    void onComplete();

    void onError(@NonNull Throwable var1);
}

  Completable是您可能不会经常使用的东西。 您可以通过调用Completable.complete()或Completable.fromRunnable()快速构建一个。 前者将不做任何事情立即调用onComplete(),而fromRunnable()将在调用onComplete()之前执行指定的操作:

import io.reactivex.Completable;

public class Ch2_32 {
    public static void main(String[] args) {
        Completable.fromRunnable(() -> runProcess())
                .subscribe(() -> System.out.println("Done!"));
    }

    public static void runProcess() {
        //run process here
    }
}

  输出如下:Done!

六、Disposing(处置)

  当您对一个Observable subscribe()来接收发射时,将创建一个流以通过Observable链处理这些发射。当然,这会占用资源。完成后,我们希望处置这些资源,以便可以对其进行垃圾回收。幸运的是,有限的调用onComplete(),Observable通常会在完成时安全地自行处置。但是,如果您使用的是无限或长期运行的Observable,则可能会遇到想要显式停止发射并处置与该订阅关联的所有内容的情况。实际上,您不能信任垃圾收集器来处理不再需要的活动订阅,并且有必要进行显式处理以防止内存泄漏。Disposable是Observable和活动Observer之间的链接,您可以调用其dispose()方法来停止发射并处置用于该Observer的所有资源。它还具有isDisposed()方法,指示是否已将其丢弃:

public interface Disposable {
    void dispose();

    boolean isDisposed();
}

  当提供onNext(),onComplete()和/或onError()lambda作为subscribe()方法的参数时,它实际上将返回Disposable。 您可以随时通过调用其dispose()方法来停止发射。 例如,我们可以在五秒钟后停止接收来自Observable.interval()的发射:

import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;

import java.util.concurrent.TimeUnit;

public class Ch2_33 {
    public static void main(String[] args) {
        Observable<Long> seconds =
                Observable.interval(1, TimeUnit.SECONDS);
        Disposable disposable =
                seconds.subscribe(l -> System.out.println("Received: " + l));
        //sleep 5 seconds
        sleep(5000);
        //处理和停止发射
        disposable.dispose();
        //将没有更多的发射
        sleep(5000);
    }

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

  在这里,我们让Observable.interval()与一个Observer一起运行五秒钟,但是我们保存了subscribe()方法返回的Disposable。 然后,我们调用Disposable的dispose()方法来停止该进程并释放所有正在使用的资源。 然后,我们再睡五秒钟,以证明不再有发射发生。

6.1、在观察者内处理Disposable

  之前,我回避谈论Observer中的onSubscribe()方法,但是现在我们将解决它。 您可能已经注意到,在Observer的实现中通过onSubscribe()方法传递了Disposable。 此方法在RxJava 2.0中已添加,它使Observer可以随时处理预订。

  例如,您可以实现自己的Observer并使用onNext(),onComplete()或onError()来访问Disposable。这样,如果出于某种原因不想再发射了,这三个事件都可以调用dispose():
Disposable从源头一直发送到观察者链,因此Observable链中的每个步骤都可以访问Disposable。

  请注意,将Observer传递给subscribe()方法将是无效的,并且不会返回Disposable,因为Observer可能处理它。 如果您不想明确地处理Disposable并希望RxJava为您处理它(在您有控制权之前,这可能是一个好主意),则可以将ResourceObserver扩展为Observer,它使用默认的Disposable处理。 将其传递给subscribeWith()而不是subscribe(),您将获得默认的Disposable返回:

import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.observers.ResourceObserver;

import java.util.concurrent.TimeUnit;

public class Ch2_34 {
    public static void main(String[] args) {
        Observable<Long> source =
                Observable.interval(1, TimeUnit.SECONDS);
        ResourceObserver<Long> myObserver = new
                ResourceObserver<Long>() {
                    @Override
                    public void onNext(Long value) {
                        System.out.println(value);
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("Done!");
                    }
                };
        //capture Disposable
        Disposable disposable = source.subscribeWith(myObserver);
    }
}
6.2、使用CompositeDisposable

  如果您需要管理和处置多个订阅,则使用CompositeDisposable会有所帮助。 它实现了Disposable,但在内部保留了一组Disposable,您可以将这些Disposable添加到其中,然后立即将其全部处置:

import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;

import java.util.concurrent.TimeUnit;

public class Ch2_35 {
    private static final CompositeDisposable disposables
            = new CompositeDisposable();

    public static void main(String[] args) {
        Observable<Long> seconds =
                Observable.interval(1, TimeUnit.SECONDS);
        //subscribe and capture disposables
        Disposable disposable1 =
                seconds.subscribe(l -> System.out.println("Observer 1: " +
                        l));
        Disposable disposable2 =
                seconds.subscribe(l -> System.out.println("Observer 2: " +
                        l));
        //put both disposables into CompositeDisposable
        disposables.addAll(disposable1, disposable2);
        //sleep 5 seconds
        sleep(5000);
        //dispose all disposables
        disposables.dispose();
        //sleep 5 seconds to prove
        //there are no more emissions
        sleep(5000);
    }

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

  CompositeDisposable是一个简单但有用的实用程序,用于维护可以通过调用add()或addAll()添加到Disposable的集合。 当您不再需要这些订阅时,可以调用dispose()一次处理所有订阅。

6.3、使用Observable.create()处理Disposable

  如果您的Observable.create()返回一个长期运行或无限的Observable,则理想情况下,应定期检查ObservableEmitter的isDisposed()方法,以查看是否应继续发送发射。 如果订阅不再有效,这可以避免不必要的工作。在这种情况下,您应该使用Observable.range(),但是为了示例起见,假设我们在Observable.create()的for循环中发出整数。 发出每个整数之前,应确保ObservableEmitter不指示已调用处置:

import io.reactivex.Observable;

public class Ch2_36 {
    public static void main(String[] args) {
        Observable<Integer> source =
                Observable.create(observableEmitter -> {
                    try {
                        for (int i = 0; i < 1000; i++) {
                            while (!observableEmitter.isDisposed()) {
                                observableEmitter.onNext(i);
                            }
                            if (observableEmitter.isDisposed())
                                return;
                        }
                        observableEmitter.onComplete();
                    } catch (Throwable e) {
                        observableEmitter.onError(e);
                    }
                });
    }
}

  如果Observable.create()包裹在某些资源周围,则还应该处理该资源以防止泄漏。 ObservableEmitter具有setCancellable()和setDisposable()方法。 在较早的JavaFX示例中,发生处置时,应从JavaFX ObservableValue中删除ChangeListener。 我们可以为setCancellable()提供一个lambda,它将为我们执行以下操作,该操作将在调用dispose()时发生:

七、总结

  这是一章很紧凑的章节,但是当您学习如何使用RxJava处理实际工作时,它将提供坚实的基础。 RxJava具有所有表现力,它们的细微差别完全是由于其所要求的思维方式的改变。 它采用了像Java这样的命令性语言并将其改编为响应式和功能性,从而完成了大量令人印象深刻的工作。 但是,这种互操作性需要对Observable和Observer之间的实现有一些了解。 我们通过各种方式来创建Observable以及它们与Observer的交互方式。

  花些时间尝试消化所有这些信息,但不要让它阻止您继续进行下一两章,在此,RxJava的实用性开始形成。 在随后的各章中,RxJava的实用实用性将开始变得清晰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值