Математические и агрегатные операторы в RxJava

Математические и агрегатные операторы в RxJava

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

После статьиintroduction to RxJava мы рассмотрим агрегатные и математические операторы.

These operations must wait for the source Observable to emit all items. Из-за этого эти операторы опасно использовать вObservables, который может представлять очень длинные или бесконечные последовательности.

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

Теперь давайте начнем с математических операторов.

2. Настроить

Чтобы использовать дополнительные операторы, нам нужно преобразоватьadd the additional dependency вpom.xml:


    io.reactivex
    rxjava-math
    1.0.0

Или для проекта Gradle:

compile 'io.reactivex:rxjava-math:1.0.0'

3. Математические операторы

The MathObservable is dedicated to performing mathematical operations и его операторы используют другойObservable, который выдает элементы, которые можно оценить как числа.

3.1. Averageс

Операторaverage выдает одно значение - среднее всех значений, выдаваемых источником.

Посмотрим, как это работает:

Observable sourceObservable = Observable.range(1, 20);
TestSubscriber subscriber = TestSubscriber.create();

MathObservable.averageInteger(sourceObservable).subscribe(subscriber);

subscriber.assertValue(10);

Есть четыре подобных оператора для работы с примитивными значениями:averageInteger,averageLong,averageFloat иaverageDouble.

3.2. Maxс

Операторmax выдает наибольшее встреченное число.

Посмотрим, как это работает:

Observable sourceObservable = Observable.range(1, 20);
TestSubscriber subscriber = TestSubscriber.create();

MathObservable.max(sourceObservable).subscribe(subscriber);

subscriber.assertValue(9);

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

Учитывая тот факт, что математические операторы также могут работать с объектами, которыми можно управлять как числами, перегруженный операторmax позволяет сравнивать настраиваемые типы или настраивать сортировку стандартных типов.

Определим классItem:

class Item {
    private Integer id;

    // standard constructors, getter, and setter
}

Теперь мы можем определитьitemObservable, а затем использовать операторmax, чтобы выдатьItem с наивысшимid:

Item five = new Item(5);
List list = Arrays.asList(
  new Item(1),
  new Item(2),
  new Item(3),
  new Item(4),
  five);
Observable itemObservable = Observable.from(list);

TestSubscriber subscriber = TestSubscriber.create();

MathObservable.from(itemObservable)
  .max(Comparator.comparing(Item::getId))
  .subscribe(subscriber);

subscriber.assertValue(five);

3.3. Minс

Операторmin __ генерирует один элемент, содержащий наименьший элемент из источника:

Observable sourceObservable = Observable.range(1, 20);
TestSubscriber subscriber = TestSubscriber.create();

MathObservable.min(sourceObservable).subscribe(subscriber);

subscriber.assertValue(1);

Операторmin имеет перегруженный метод, который принимает экземпляр компаратора:

Item one = new Item(1);
List list = Arrays.asList(
  one,
  new Item(2),
  new Item(3),
  new Item(4),
  new Item(5));
TestSubscriber subscriber = TestSubscriber.create();
Observable itemObservable = Observable.from(list);

MathObservable.from(itemObservable)
  .min(Comparator.comparing(Item::getId))
  .subscribe(subscriber);

subscriber.assertValue(one);

3.4. Sumс

Операторsum генерирует одно значение, которое представляет собой сумму всех чисел, выданных источникомObservable:

Observable sourceObservable = Observable.range(1, 20);
TestSubscriber subscriber = TestSubscriber.create();

MathObservable.sumInteger(sourceObservable).subscribe(subscriber);

subscriber.assertValue(210);

Существуют также похожие на примитивы операторы:sumInteger,sumLong,sumFloat иsumDouble.

4. Агрегатные операторы

4.1. Concatс

Операторconcat объединяет элементы, отправленные источником, вместе.

Теперь давайте определим дваObservables и объединим их:

List listOne = Arrays.asList(1, 2, 3, 4);
Observable observableOne = Observable.from(listOne);

List listTwo = Arrays.asList(5, 6, 7, 8);
Observable observableTwo = Observable.from(listTwo);

TestSubscriber subscriber = TestSubscriber.create();

Observable concatObservable = observableOne
  .concatWith(observableTwo);

concatObservable.subscribe(subscriber);

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

Если вдаваться в подробности, операторconcat ждет с подпиской на каждый дополнительныйObservable, который ему передается, пока не завершится предыдущий.

По этой причине конкатенация «горячего»Observable,, который немедленно начинает выдавать элементы, приведет к потере любых элементов, которые испускает «горячий»Observable, до того, как будут завершены все предыдущие.

4.2. Countс

Операторcount генерирует количество всех элементов, выданных источником:

Давайте посчитаем количество элементов, выпущенныхObservable:

List lettersList = Arrays.asList(
  "A", "B", "C", "D", "E", "F", "G");
TestSubscriber subscriber = TestSubscriber.create();

Observable sourceObservable = Observable
  .from(lettersList).count();
sourceObservable.subscribe(subscriber);

subscriber.assertValue(7);

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

Для операцииcount существует также операторcountLong, который в конце выдает значениеLong для тех последовательностей, которые могут превышать емкостьInteger.

4.3. Reduceс

Операторreduce сокращает все выдаваемые элементы в один элемент, применяя функцию аккумулятора.

Этот процесс продолжается до тех пор, пока не будут выданы все элементы, а затемObservable, изreduce, выдаст окончательное значение, возвращаемое функцией.

Теперь давайте посмотрим, как можно сократить списокString, объединив их в обратном порядке:

List list = Arrays.asList("A", "B", "C", "D", "E", "F", "G");
TestSubscriber subscriber = TestSubscriber.create();

Observable reduceObservable = Observable.from(list)
  .reduce((letter1, letter2) -> letter2 + letter1);
reduceObservable.subscribe(subscriber);

subscriber.assertValue("GFEDCBA");

4.4. Collectс

Операторcollect похож на операторreduce, но предназначен для сбора элементов в единую изменяемую структуру данных.

Требуется два параметра:

  • функция, которая возвращает пустую изменяемую структуру данных

  • функция, которая при задании структуры данных и испускаемого элемента соответствующим образом изменяет структуру данных

Давайте посмотрим, как можно вернутьset элементов изObservable:

List list = Arrays.asList("A", "B", "C", "B", "B", "A", "D");
TestSubscriber subscriber = TestSubscriber.create();

Observable> reduceListObservable = Observable
  .from(list)
  .collect(HashSet::new, HashSet::add);
reduceListObservable.subscribe(subscriber);

subscriber.assertValues(new HashSet(list));

4.5. ToListс

ОператорtoList работает так же, как операцияcollect, но собирает все элементы в один список - подумайте оCollectors.toList() из Stream API:

Observable sourceObservable = Observable.range(1, 5);
TestSubscriber subscriber = TestSubscriber.create();

Observable> listObservable = sourceObservable
  .toList();
listObservable.subscribe(subscriber);

subscriber.assertValue(Arrays.asList(1, 2, 3, 4, 5));

4.6. ToSortedListс

Как и в предыдущем примере, но список рассылки отсортирован:

Observable sourceObservable = Observable.range(10, 5);
TestSubscriber subscriber = TestSubscriber.create();

Observable> listObservable = sourceObservable
  .toSortedList();
listObservable.subscribe(subscriber);

subscriber.assertValue(Arrays.asList(10, 11, 12, 13, 14));

Как мы видим,toSortedList использует сравнение по умолчанию, но можно предоставить настраиваемую функцию компаратора. Теперь мы можем увидеть, как можно отсортировать целые числа в обратном порядке с помощью специальной функции сортировки:

Observable sourceObservable = Observable.range(10, 5);
TestSubscriber subscriber = TestSubscriber.create();

Observable> listObservable
  = sourceObservable.toSortedList((int1, int2) -> int2 - int1);
listObservable.subscribe(subscriber);

subscriber.assertValue(Arrays.asList(14, 13, 12, 11, 10));

4.7. ToMapс

ОператорtoMap преобразует последовательность элементов, выдаваемыхObservable, в карту, привязанную к указанной ключевой функции.

В частности, операторtoMap имеет разные перегруженные методы, для которых требуется один, два или три из следующих параметров:

  1. keySelector, который производит ключ из элемента

  2. valueSelector, который производит из выданного элемента фактическое значение, которое будет сохранено на карте

  3. mapFactory, который создает коллекцию, которая будет содержать элементы

Начнем с определения простого классаBook:

class Book {
    private String title;
    private Integer year;

    // standard constructors, getters, and setters
}

Теперь мы можем увидеть, как можно преобразовать серию выданных элементовBook вMap, имея название книги в качестве ключа и год в качестве значения:.

Observable bookObservable = Observable.just(
  new Book("The North Water", 2016),
  new Book("Origin", 2017),
  new Book("Sleeping Beauties", 2017)
);
TestSubscriber subscriber = TestSubscriber.create();

Observable> mapObservable = bookObservable
  .toMap(Book::getTitle, Book::getYear, HashMap::new);
mapObservable.subscribe(subscriber);

subscriber.assertValue(new HashMap() {{
  put("The North Water", 2016);
  put("Origin", 2017);
  put("Sleeping Beauties", 2017);
}});

4.8. ToMultiMapс

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

Этого можно достичь с помощью оператораtoMultiMap, который преобразует последовательность элементов, выдаваемыхObservable, вList, который также является картой, на которую вводится указанная функция ключа.

Этот оператор добавляет еще один параметр к параметрам оператораtoMap,collectionFactory.. Этот параметр позволяет указать, в каком типе коллекции должно храниться значение. Посмотрим, как это можно сделать:

Observable bookObservable = Observable.just(
  new Book("The North Water", 2016),
  new Book("Origin", 2017),
  new Book("Sleeping Beauties", 2017)
);
TestSubscriber subscriber = TestSubscriber.create();

Observable multiMapObservable = bookObservable.toMultimap(
  Book::getYear,
  Book::getTitle,
  () -> new HashMap<>(),
  (key) -> new ArrayList<>()
);
multiMapObservable.subscribe(subscriber);

subscriber.assertValue(new HashMap() {{
    put(2016, Arrays.asList("The North Water"));
    put(2017, Arrays.asList("Origin", "Sleeping Beauties"));
}});

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

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

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