Фильтрация наблюдаемых в RxJava

Фильтрация наблюдаемых в RxJava

1. Вступление

ПослеIntroduction to RxJava мы рассмотрим операторы фильтрации.

В частности, мы сосредоточимся на фильтрации, пропуске, временной фильтрации и некоторых более сложных операциях фильтрации.

2. фильтрация

При работе сObservable иногда бывает полезно выбрать только подмножество отправленных элементов. Для этогоRxJava offers various filtering capabilities.

Давайте начнем с методаfilter.

2.1. Операторfilter

Simply put, the filter operator filters an Observable making sure that emitted items match specified condition, который имеет вид aPredicate.

Давайте посмотрим, как мы можем отфильтровать только нечетные значения из выданных:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .filter(i -> i % 2 != 0);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(1, 3, 5, 7, 9);

2.2. Операторtake

При фильтрации с помощьюtake логика приводит к выдаче первых элементовn при игнорировании остальных элементов.

Давайте посмотрим, как мы можем отфильтроватьsourceObservable и выдать только первые два элемента:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.take(3);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 3);

2.3. ОператорtakeWhile

При использованииtakeWhile, отфильтрованныйObservable будет продолжать испускать элементы, пока не встретит первый элемент, который не соответствуетPredicate.

Давайте посмотрим, как мы можем использоватьtakeWhile - с фильтрациейPredicate:

Observable sourceObservable = Observable.just(1, 2, 3, 4, 3, 2, 1);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .takeWhile(i -> i < 4);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 3);

2.4. ОператорtakeFirst

Когда мы хотим испустить только первый элемент, соответствующий заданному условию, мы можем использоватьtakeFirst().

Давайте быстро посмотрим, как мы можем создать первый элемент, который больше 5:

Observable sourceObservable = Observable
  .just(1, 2, 3, 4, 5, 7, 6);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .takeFirst(x -> x > 5);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(7);

2.5. Операторыfirst иfirstOrDefault

Подобного поведения можно добиться с помощью APIfirst:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.first();

filteredObservable.subscribe(subscriber);

subscriber.assertValue(1);

Однако, если мы хотим указать значение по умолчанию, если никакие элементы не отправляются, мы можем использоватьfirstOrDefault:

Observable sourceObservable = Observable.empty();

Observable filteredObservable = sourceObservable.firstOrDefault(-1);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(-1);

2.6. ОператорtakeLast

Затем, если мы хотим испускать только последние элементыn, выпущенныеObservable, мы можем использоватьtakeLast.

Давайте посмотрим, как можно выдать только три последних элемента:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.takeLast(3);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(8, 9, 10);

Мы должны помнить, что это задерживает передачу любого элемента из источникаObservable до его завершения.

2.7. last иlastOrDefault

Если мы хотим испустить только последний элемент, кромеtakeLast(1), мы можем использоватьlast.

Это фильтруетObservable, выделяя только последний элемент, который при необходимости проверяет фильтрациюPredicate:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .last(i -> i % 2 != 0);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(9);

В случае, еслиObservable пуст, мы можем использоватьlastOrDefault, который фильтруетObservable, выдавая значение по умолчанию.

Значение по умолчанию также выдается, если используется операторlastOrDefault и нет никаких элементов, которые проверяют условие фильтрации:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable =
  sourceObservable.lastOrDefault(-1, i -> i > 10);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(-1);

2.8. ОператорыelementAt иelementAtOrDefault

С помощью оператораelementAt мы можем выбрать один элемент, выданный источникомObservable, указав его индекс:

Observable sourceObservable = Observable
  .just(1, 2, 3, 5, 7, 11);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.elementAt(4);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(7);

ОднакоelementAt выдастIndexOutOfBoundException, если указанный индекс превышает количество отправленных элементов.

Чтобы избежать этой ситуации, можно использоватьelementAtOrDefault –, который вернет значение по умолчанию, если индекс выходит за пределы допустимого диапазона:

Observable sourceObservable = Observable
  .just(1, 2, 3, 5, 7, 11);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable
 = sourceObservable.elementAtOrDefault(7, -1);

filteredObservable.subscribe(subscriber);

subscriber.assertValue(-1);

2.9. ОператорofType

Каждый раз, когдаObservable испускает элементыObject, их можно фильтровать по их типу.

Давайте посмотрим, как мы можем отфильтровать только выданные элементы типаString:

Observable sourceObservable = Observable.just(1, "two", 3, "five", 7, 11);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.ofType(String.class);

filteredObservable.subscribe(subscriber);

subscriber.assertValues("two", "five");

3. Пропуская

С другой стороны, когда мы хотим отфильтровать или пропустить некоторые элементы, испускаемыеObservable,RxJava offers a few operators as a counterpart of the filtering ones, которые мы обсуждали ранее.

Давайте начнем с оператораskip, аналогtake.

3.1. Операторskip

КогдаObservable испускает последовательность элементов, можно отфильтровать или пропустить некоторые из первых отправленных элементов с помощьюskip.

Например. давайте посмотрим, как можно пропустить первые четыре элемента:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.skip(4);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(5, 6, 7, 8, 9, 10);

3.2. ОператорskipWhile

Всякий раз, когда мы хотим отфильтровать все первые значения, выдаваемыеObservable, которые не соответствуют предикату фильтрации, мы можем использовать операторskipWhile:

Observable sourceObservable = Observable
  .just(1, 2, 3, 4, 5, 4, 3, 2, 1);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable
  .skipWhile(i -> i < 4);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(4, 5, 4, 3, 2, 1);

3.3. TheskipLast Оператор

ОператорskipLast позволяет нам пропускать последние элементы, испускаемыеObservable, принимая только те, которые были отправлены до них.

При этом мы можем, например, пропустить последние пять пунктов:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = sourceObservable.skipLast(5);

filteredObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 3, 4, 5);

3.4. Операторыdistinct иdistinctUntilChanged

Операторdistinct возвращаетObservable, который испускает все элементы, испускаемыеsourceObservable, которые являются различными:

Observable sourceObservable = Observable
  .just(1, 1, 2, 2, 1, 3, 3, 1);
TestSubscriber subscriber = new TestSubscriber();

Observable distinctObservable = sourceObservable.distinct();

distinctObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 3);

Однако, если мы хотим получитьObservable, который испускает все элементы, испускаемыеsourceObservable, которые отличаются от их непосредственного предшественника, мы можем использовать операторdistinctUntilChanged:

Observable sourceObservable = Observable
  .just(1, 1, 2, 2, 1, 3, 3, 1);
TestSubscriber subscriber = new TestSubscriber();

Observable distinctObservable = sourceObservable.distinctUntilChanged();

distinctObservable.subscribe(subscriber);

subscriber.assertValues(1, 2, 1, 3, 1);

3.5. ОператорignoreElements

Всякий раз, когда мы хотим игнорировать все элементы, испускаемыеsourceObservable, мы можем просто использоватьignoreElements:

Observable sourceObservable = Observable.range(1, 10);
TestSubscriber subscriber = new TestSubscriber();

Observable ignoredObservable = sourceObservable.ignoreElements();

ignoredObservable.subscribe(subscriber);

subscriber.assertNoValues();

4. Операторы фильтрации времени

При работе с наблюдаемой последовательностью временная ось неизвестна, но иногда бывает полезно получить своевременные данные из последовательности.

Для этогоRxJava offers a few methods that allow us to work with Observable using also the time axis.

Прежде чем перейти к первому, давайте определим рассчитанный по времениObservable, который будет выдавать элемент каждую секунду:

TestScheduler testScheduler = new TestScheduler();

Observable timedObservable = Observable
  .just(1, 2, 3, 4, 5, 6)
  .zipWith(Observable.interval(
    0, 1, TimeUnit.SECONDS, testScheduler), (item, time) -> item);

The TestScheduler is a special scheduler that allows advancing the clock manually в любом темпе, который мы предпочитаем.

4.1. Операторыsample иthrottleLast

Операторsample фильтруетtimedObservable, возвращаяObservable, который генерирует самые последние элементы, выданные этим API в течение временных интервалов.

Давайте посмотрим, как мы можем выбратьtimedObservable, фильтруя только последний отправленный элемент каждые 2,5 секунды:

TestSubscriber subscriber = new TestSubscriber();

Observable sampledObservable = timedObservable
  .sample(2500L, TimeUnit.MILLISECONDS, testScheduler);

sampledObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValues(3, 5, 6);

Такого поведения можно также добиться с помощью оператораthrottleLast.

4.2. ОператорthrottleFirst

ОператорthrottleFirst отличается отthrottleLast/sample, поскольку он испускает первый элемент, испускаемыйtimedObservable в каждом периоде выборки, а не последний отправленный.

Давайте посмотрим, как мы можем выдать первые элементы, используя период выборки в 4 секунды:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .throttleFirst(4100L, TimeUnit.SECONDS, testScheduler);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValues(1, 6);

4.3. Операторыdebounce иthrottleWithTimeout

С помощью оператораdebounce можно выдать только элемент, если определенный промежуток времени прошел, без выдачи другого элемента.

Следовательно, если мы выберем интервал времени, который больше, чем интервал времени между отправленными элементамиtimedObservable, он будет выдавать только последний.. С другой стороны, если он меньше, он будет излучать все элементы, испускаемыеtimedObservable.

Посмотрим, что происходит в первом сценарии:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .debounce(2000L, TimeUnit.MILLISECONDS, testScheduler);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValue(6);

Такого же поведения можно добиться с помощьюthrottleWithTimeout.

4.4. Операторtimeout

Операторtimeout отражает источникObservable, но выдает ошибку уведомления, прерывая передачу элементов, если источникObservable не может передать какие-либо элементы в течение заданного интервала времени.

Давайте посмотрим, что произойдет, если мы укажем тайм-аут в 500 миллисекунд для нашегоtimedObservable:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .timeout(500L, TimeUnit.MILLISECONDS, testScheduler);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertError(TimeoutException.class); subscriber.assertValues(1);

5. Множественная наблюдаемая фильтрация

При работе сObservable определенно можно решить, фильтровать или пропускать элементы на основе второгоObservable.

Прежде чем двигаться дальше, давайте определимdelayedObservable, который выдаст только 1 элемент через 3 секунды:

Observable delayedObservable = Observable.just(1)
  .delay(3, TimeUnit.SECONDS, testScheduler);

Начнем с оператораtakeUntil.

5.1. ОператорtakeUntil

ОператорtakeUntil отбрасывает любой элемент, выданный источникомObservable (timedObservable) после того, как секундаObservable (delayedObservable) испускает элемент или завершается:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .skipUntil(delayedObservable);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValues(4, 5, 6);

5.2. ОператорskipUntil

С другой стороны,skipUntil отбрасывает любой элемент, выданный источникомObservable (timedObservable), пока второйObservable (delayedObservable) не испускает элемент:

TestSubscriber subscriber = new TestSubscriber();

Observable filteredObservable = timedObservable
  .takeUntil(delayedObservable);

filteredObservable.subscribe(subscriber);

testScheduler.advanceTimeBy(7, TimeUnit.SECONDS);

subscriber.assertValues(1, 2, 3);

6. Заключение

В этом обширном руководстве мы рассмотрели различные операторы фильтрации, доступные в RxJava, предоставив простой пример каждого из них.

Как всегда, все примеры кода в этой статье можно найтиover on GitHub.