Математические и агрегатные операторы в 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 имеет разные перегруженные методы, для которых требуется один, два или три из следующих параметров:
-
keySelector, который производит ключ из элемента
-
valueSelector, который производит из выданного элемента фактическое значение, которое будет сохранено на карте
-
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
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.