RxJavaで観測可能なユーティリティ演算子

RxJavaの観察可能なユーティリティオペレーター

1. 概要

この記事では、RxJavaでObservablesを操作するためのユーティリティ演算子と、カスタム演算子を実装する方法について説明します。

An operator is a function that takes and alters the behavior of an upstream Observable<T> and returns a downstream Observable<R> or Subscriber、タイプTとRは同じである場合と同じでない場合があります。

演算子は既存のObservablesをラップし、通常はサブスクリプションをインターセプトすることによってそれらを拡張します。 これは複雑に聞こえるかもしれませんが、実際には非常に柔軟であり、把握するのは難しくありません。

2. Do

Observableのライフサイクルイベントを変更する可能性のあるアクションは複数あります。

doOnNext演算子は、Observableソース so that it invokes an action when the onNext is calledを変更します。__

The doOnCompleted operator registers an action which is called if the resulting Observable terminates normallyObserveronCompletedメソッド:を呼び出す

Observable.range(1, 10)
  .doOnNext(r -> receivedTotal += r)
  .doOnCompleted(() -> result = "Completed")
  .subscribe();

assertTrue(receivedTotal == 55);
assertTrue(result.equals("Completed"));

doOnEach演算子は、Observableソースを変更して、各アイテムのObserverに通知し、アイテムが発行されるたびに呼び出されるコールバックを確立します。

doOnSubscribe演算子は、Observerが結果のObservableにサブスクライブするたびに呼び出されるアクションを登録します。

doOnSubscribe:の反対を行うdoOnUnsubscribe operatorもあります

Observable.range(1, 10)
  .doOnEach(new Observer() {
      @Override
      public void onCompleted() {
          System.out.println("Complete");
      }
      @Override
      public void onError(Throwable e) {
          e.printStackTrace();
      }
      @Override
      public void onNext(Integer value) {
          receivedTotal += value;
      }
  })
  .doOnSubscribe(() -> result = "Subscribed")
  .subscribe();
assertTrue(receivedTotal == 55);
assertTrue(result.equals("Subscribed"));

Observableがエラーで完了した場合、doOnError演算子を使用してアクションを実行できます。

DoOnTerminate operatorは、Observableが正常に完了するか、エラーが発生したときに呼び出されるアクションを登録します。

thrown.expect(OnErrorNotImplementedException.class);
Observable.empty()
  .single()
  .doOnError(throwable -> { throw new RuntimeException("error");})
  .doOnTerminate(() -> result += "doOnTerminate")
  .doAfterTerminate(() -> result += "_doAfterTerminate")
  .subscribe();
assertTrue(result.equals("doOnTerminate_doAfterTerminate"));

FinallyDo operator – which was deprecated in favor of doAfterTerminate. It registers an action when an Observable completes.もあります

3. ObserveOnSubscribeOn

デフォルトでは、Observableと演算子チェーンは、そのSubscribeメソッドが呼び出されたのと同じスレッドで動作します。

[.operator]#ObserveOn #operatorは、ObservableObservers:に通知を送信するために使用する別のSchedulerを指定します

Observable.range(1, 5)
  .map(i -> i * 100)
  .doOnNext(i -> {
      emittedTotal += i;
      System.out.println("Emitting " + i
        + " on thread " + Thread.currentThread().getName());
  })
  .observeOn(Schedulers.computation())
  .map(i -> i * 10)
  .subscribe(i -> {
      receivedTotal += i;
      System.out.println("Received " + i + " on thread "
        + Thread.currentThread().getName());
  });

Thread.sleep(2000);
assertTrue(emittedTotal == 1500);
assertTrue(receivedTotal == 15000);

要素がmain threadで生成され、最初のmap呼び出しまでプッシュされたことがわかります。

しかしその後、observeOnは処理をcomputation threadにリダイレクトしました。これは、mapと最後のSubscriber.を処理するときに使用されました。

One problem that may arise with observeOn is the bottom stream can produce emissions faster than the top stream can process them.これにより、backpressureで問題が発生する可能性があります。

どのSchedulerObservableを操作するかを指定するには、subscribeOn演算子を使用できます。

Observable.range(1, 5)
  .map(i -> i * 100)
  .doOnNext(i -> {
      emittedTotal += i;
      System.out.println("Emitting " + i
        + " on thread " + Thread.currentThread().getName());
  })
  .subscribeOn(Schedulers.computation())
  .map(i -> i * 10)
  .subscribe(i -> {
      receivedTotal += i;
      System.out.println("Received " + i + " on thread "
        + Thread.currentThread().getName());
  });

Thread.sleep(2000);
assertTrue(emittedTotal == 1500);
assertTrue(receivedTotal == 15000);

SubscribeOnは、アイテムの発行に使用するスレッドをソースObservableに指示します。このスレッドのみがアイテムをSubscriberにプッシュします。 サブスクリプションにのみ影響するため、ストリーム内の任意の場所に配置できます。

事実上、使用できるsubscribeOnは1つだけですが、observeOn演算子はいくつでも使用できます。 observeOn.を使用すると、排出量をあるスレッドから別のスレッドに簡単に切り替えることができます。

4. SingleおよびSingleOrDefault

演算子Singleは、ソースObservable:によって発行された単一のアイテムを発行するObservableを返します。

Observable.range(1, 1)
  .single()
  .subscribe(i -> receivedTotal += i);
assertTrue(receivedTotal == 1);

ソースObservableがゼロまたは複数の要素を生成する場合、例外がスローされます。

Observable.empty()
  .single()
  .onErrorReturn(e -> receivedTotal += 10)
  .subscribe();
assertTrue(receivedTotal == 10);

一方、演算子SingleOrDefaultSingle,と非常によく似ています。つまり、ソースから単一のアイテムを出力するObservableも返しますが、さらにデフォルト値を指定できます。 :

Observable.empty()
  .singleOrDefault("Default")
  .subscribe(i -> result +=i);
assertTrue(result.equals("Default"));

ただし、Observableソースが複数のアイテムを放出する場合でも、IllegalArgumentExeption:をスローします。

Observable.range(1, 3)
  .singleOrDefault(5)
  .onErrorReturn(e -> receivedTotal += 10)
  .subscribe();
assertTrue(receivedTotal == 10);

簡単な結論:

  • ソースObservableに要素がないか、要素が1つあると予想される場合は、SingleOrDefaultを使用する必要があります。

  • `Observable`で出力される可能性のある複数のアイテムを処理していて、最初または最後の値のみを出力する場合は、firstlastなどの他の演算子を使用できます。

5. Timestamp

そのアイテムを独自のシーケンスで再送信する前のThe Timestamp operator attaches a timestamp to each item emitted by the source Observable。 タイムスタンプは、アイテムが発行された時刻を示します。

Observable.range(1, 10)
  .timestamp()
  .map(o -> result = o.getClass().toString() )
  .last()
  .subscribe();

assertTrue(result.equals("class rx.schedulers.Timestamped"));

6. Delay

この演算子は、ソースObservable’sの各アイテムを発行する前に、特定の時間増分で一時停止することにより、ソースObservableを変更します。

指定された値を使用してシーケンス全体をオフセットします。

Observable source = Observable.interval(1, TimeUnit.SECONDS)
  .take(5)
  .timestamp();

Observable delayedObservable
  = source.delay(2, TimeUnit.SECONDS);

source.subscribe(
  value -> System.out.println("source :" + value),
  t -> System.out.println("source error"),
  () -> System.out.println("source completed"));

delayedObservable.subscribe(
  value -> System.out.println("delay : " + value),
  t -> System.out.println("delay error"),
  () -> System.out.println("delay completed"));
Thread.sleep(8000);

delaySubscriptionと呼ばれるソースObservableへのサブスクリプションを遅らせることができる代替演算子があります。

Delay演算子はデフォルトでcomputationSchedulerで実行されますが、オプションの3番目のパラメーターとしてdelaySubscriptionに渡すことにより、別のSchedulerを選択できます。

7. Repeat

Repeatは、アップストリームからの完了通知を単にインターセプトし、ダウンストリームに渡すのではなく、再サブスクライブします。

したがって、repeatが同じ一連のイベントを循環し続けることは保証されませんが、アップストリームが固定ストリームの場合は発生します。

Observable.range(1, 3)
  .repeat(3)
  .subscribe(i -> receivedTotal += i);

assertTrue(receivedTotal == 18);

8. Cache

cache演算子は、subscribeとカスタムObservableの間にあります。

最初のサブスクライバーが表示されると、cacheはサブスクリプションを基になるObservableに委任し、すべての通知(イベント、完了、またはエラー)をダウンストリームに転送します。

ただし、同時に、すべての通知のコピーを内部的に保持します。 後続のサブスクライバーがプッシュされた通知を受信する場合、cacheは基になるObservableに委任せず、代わりにキャッシュされた値をフィードします。

Observable source =
  Observable.create(subscriber -> {
      System.out.println("Create");
      subscriber.onNext(receivedTotal += 5);
      subscriber.onCompleted();
  }).cache();
source.subscribe(i -> {
  System.out.println("element 1");
  receivedTotal += 1;
});
source.subscribe(i -> {
  System.out.println("element 2");
  receivedTotal += 2;
});

assertTrue(receivedTotal == 8);

9. Using

observerusing()から返されたObservableをサブスクライブすると、Observableファクトリ関数を使用してObservableobserverが作成されます。 …観察すると同時に、リソースファクトリ関数を使用して、設計したリソースを作成します。

observerObservableからサブスクライブを解除するとき、またはObservableが終了するとき、usingは3番目の関数を呼び出して、作成されたリソースを破棄します。

Observable values = Observable.using(
  () -> "resource",
  r -> {
      return Observable.create(o -> {
          for (Character c : r.toCharArray()) {
              o.onNext(c);
          }
          o.onCompleted();
      });
  },
  r -> System.out.println("Disposed: " + r)
);
values.subscribe(
  v -> result += v,
  e -> result += e
);
assertTrue(result.equals("resource"));

10. 結論

この記事では、RxJavaユーティリティ演算子の使用方法と、それらの最も重要な機能の探索方法について説明しました。

RxJavaの真の力は、その演算子にあります。 データのストリームの宣言的変換は、安全でありながら表現力と柔軟性があります。

関数型プログラミングの強力な基盤により、オペレーターはRxJavaの採用において決定的な役割を果たします。 組み込み演算子をマスターすることは、このライブラリで成功するための鍵です。

ここで使用されているすべてのコードサンプルを含むプロジェクトの完全なソースコードは、over on GitHubにあります。