1. Введение
После введения в статью о RxJava мы рассмотрим агрегатные и математические операторы.
Эти операции должны ждать, пока исходный Observable
выдаст все элементы. Из-за этого эти операторы опасно использовать с Observables
, которые могут представлять очень длинные или бесконечные последовательности.
Во-вторых, во всех примерах используется экземпляр TestSubscriber,
особая разновидность Subscriber
, которую можно использовать для модульного тестирования, выполнения утверждений, проверки полученных событий или переноса имитируемого Subscriber.
Теперь давайте рассмотрим математические операторы.
2. Настройка
Чтобы использовать дополнительные операторы, нам нужно добавить дополнительную зависимость в pom.xml:
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava-math</artifactId>
<version>1.0.0</version>
</dependency>
Или для проекта Gradle:
compile 'io.reactivex:rxjava-math:1.0.0'
3. Математические операторы
MathObservable предназначен
для выполнения математических операций , и его операторы используют другой Observable
, который выдает элементы, которые можно оценить как числа.
3.1. Средний
Оператор усреднения
выдает одно значение — среднее значение всех значений, выдаваемых источником.
Давайте посмотрим, что в действии:
Observable<Integer> sourceObservable = Observable.range(1, 20);
TestSubscriber<Integer> subscriber = TestSubscriber.create();
MathObservable.averageInteger(sourceObservable).subscribe(subscriber);
subscriber.assertValue(10);
Есть четыре похожих оператора для работы с примитивными значениями: AverageInteger
, Average
Long
, Average
Float
и Average
Double
.
3.2. Максимум
Оператор max выдает
наибольшее встречающееся число.
Давайте посмотрим, что в действии:
Observable<Integer> sourceObservable = Observable.range(1, 20);
TestSubscriber<Integer> 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
с наивысшим идентификатором
:
Item five = new Item(5);
List<Item> list = Arrays.asList(
new Item(1),
new Item(2),
new Item(3),
new Item(4),
five);
Observable<Item> itemObservable = Observable.from(list);
TestSubscriber<Item> subscriber = TestSubscriber.create();
MathObservable.from(itemObservable)
.max(Comparator.comparing(Item::getId))
.subscribe(subscriber);
subscriber.assertValue(five);
3.3. Мин.
Оператор min
`` создает один элемент, содержащий наименьший элемент из источника:
Observable<Integer> sourceObservable = Observable.range(1, 20);
TestSubscriber<Integer> subscriber = TestSubscriber.create();
MathObservable.min(sourceObservable).subscribe(subscriber);
subscriber.assertValue(1);
У оператора min
есть перегруженный метод, который принимает экземпляр компаратора:
Item one = new Item(1);
List<Item> list = Arrays.asList(
one,
new Item(2),
new Item(3),
new Item(4),
new Item(5));
TestSubscriber<Item> subscriber = TestSubscriber.create();
Observable<Item> itemObservable = Observable.from(list);
MathObservable.from(itemObservable)
.min(Comparator.comparing(Item::getId))
.subscribe(subscriber);
subscriber.assertValue(one);
3.4. Сумма
Оператор суммы
выдает единственное значение, которое представляет собой сумму всех чисел, испускаемых исходным Observable:
Observable<Integer> sourceObservable = Observable.range(1, 20);
TestSubscriber<Integer> subscriber = TestSubscriber.create();
MathObservable.sumInteger(sourceObservable).subscribe(subscriber);
subscriber.assertValue(210);
Существуют также примитивно-специализированные аналогичные операторы: sumInteger
, sum
Long
, sum
Float
и sum
Double
.
4. Агрегатные операторы
4.1. Конкат
Оператор c oncat
объединяет элементы, испускаемые источником, вместе .
Давайте теперь определим два Observable
и соединим их:
List<Integer> listOne = Arrays.asList(1, 2, 3, 4);
Observable<Integer> observableOne = Observable.from(listOne);
List<Integer> listTwo = Arrays.asList(5, 6, 7, 8);
Observable<Integer> observableTwo = Observable.from(listTwo);
TestSubscriber<Integer> subscriber = TestSubscriber.create();
Observable<Integer> concatObservable = observableOne
.concatWith(observableTwo);
concatObservable.subscribe(subscriber);
subscriber.assertValues(1, 2, 3, 4, 5, 6, 7, 8);
Если вдаваться в подробности, оператор concat
ждет с подпиской на каждый дополнительный Observable
, который передается ему, пока предыдущий не завершится.
По этой причине объединение «горячего» Observable,
который начинает немедленно испускать элементы, приведет к потере любых элементов, которые «горячий» Observable
испускает до того, как все предыдущие будут завершены.
4.2. Считать
Оператор count выдает
количество всех элементов, испускаемых источником:
Давайте подсчитаем количество элементов, испускаемых Observable
:
List<String> lettersList = Arrays.asList(
"A", "B", "C", "D", "E", "F", "G");
TestSubscriber<Integer> subscriber = TestSubscriber.create();
Observable<Integer> sourceObservable = Observable
.from(lettersList).count();
sourceObservable.subscribe(subscriber);
subscriber.assertValue(7);
Если исходный Observable
завершится с ошибкой, счетчик
передаст ошибку уведомления, не выпустив элемент. Однако, если он вообще не завершается, счет не будет ни выдавать
элемент, ни завершаться.
Для операции count
также есть оператор countLong
, который в конце выдает значение Long
для тех последовательностей, которые могут превышать емкость Integer
.
4.3. Уменьшать
Оператор редукции сводит
все испускаемые элементы в один элемент, применяя функцию аккумулятора.
Этот процесс продолжается до тех пор, пока не будут выданы все элементы, а затем Observable
из сокращения
выдает окончательное значение, возвращаемое функцией.
Теперь давайте посмотрим, как можно выполнить сокращение списка String
, объединив их в обратном порядке:
List<String> list = Arrays.asList("A", "B", "C", "D", "E", "F", "G");
TestSubscriber<String> subscriber = TestSubscriber.create();
Observable<String> reduceObservable = Observable.from(list)
.reduce((letter1, letter2) -> letter2 + letter1);
reduceObservable.subscribe(subscriber);
subscriber.assertValue("GFEDCBA");
4.4. Собирать
Оператор сбора
похож на оператор сокращения
, но предназначен для сбора элементов в единую изменяемую структуру данных.
Требует два параметра:
- функция, которая возвращает пустую изменяемую структуру данных
- функция, которая при наличии структуры данных и испускаемого элемента соответствующим образом изменяет структуру данных
Давайте посмотрим, как можно вернуть набор
элементов из Observable
:
List<String> list = Arrays.asList("A", "B", "C", "B", "B", "A", "D");
TestSubscriber<HashSet> subscriber = TestSubscriber.create();
Observable<HashSet<String>> reduceListObservable = Observable
.from(list)
.collect(HashSet::new, HashSet::add);
reduceListObservable.subscribe(subscriber);
subscriber.assertValues(new HashSet(list));
4.5. К списку
Оператор toList
работает так же, как операция сбора
, но собирает все элементы в один список — подумайте о Collectors.toList()
из Stream API:
Observable<Integer> sourceObservable = Observable.range(1, 5);
TestSubscriber<List> subscriber = TestSubscriber.create();
Observable<List<Integer>> listObservable = sourceObservable
.toList();
listObservable.subscribe(subscriber);
subscriber.assertValue(Arrays.asList(1, 2, 3, 4, 5));
4.6. ToSortedList
Как и в предыдущем примере, но испускаемый список отсортирован:
Observable<Integer> sourceObservable = Observable.range(10, 5);
TestSubscriber<List> subscriber = TestSubscriber.create();
Observable<List<Integer>> listObservable = sourceObservable
.toSortedList();
listObservable.subscribe(subscriber);
subscriber.assertValue(Arrays.asList(10, 11, 12, 13, 14));
Как мы видим, toSortedList
использует сравнение по умолчанию, но можно предоставить пользовательскую функцию сравнения. Теперь мы можем увидеть, как можно сортировать целые числа в обратном порядке, используя пользовательскую функцию сортировки:
Observable<Integer> sourceObservable = Observable.range(10, 5);
TestSubscriber<List> subscriber = TestSubscriber.create();
Observable<List<Integer>> listObservable
= sourceObservable.toSortedList((int1, int2) -> int2 - int1);
listObservable.subscribe(subscriber);
subscriber.assertValue(Arrays.asList(14, 13, 12, 11, 10));
4.7. Для отображения
Оператор toMap
преобразует последовательность элементов, испускаемых Observable
, в карту с ключом указанной ключевой функцией.
В частности, у оператора toMap
есть разные перегруженные методы, требующие одного, двух или трех из следующих параметров:
- keySelector ,
который
создает ключ из элемента селектор значений
, который создает из созданного элемента фактическое значение, которое будет храниться на карте.- mapFactory ,
которая
создает коллекцию, в которой будут храниться элементы
Начнем с определения простого класса Book
:
class Book {
private String title;
private Integer year;
// standard constructors, getters, and setters
}
Теперь мы можем увидеть, как можно преобразовать серию созданных элементов Book в
Map
, используя название книги в качестве ключа и год в качестве значения :
Observable<Book> bookObservable = Observable.just(
new Book("The North Water", 2016),
new Book("Origin", 2017),
new Book("Sleeping Beauties", 2017)
);
TestSubscriber<Map> subscriber = TestSubscriber.create();
Observable<Map<String, Integer>> 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
, в список
, который также является картой, на которую указывает указанная ключевая функция.
Этот оператор добавляет еще один параметр к параметрам оператора toMap
, collectionFactory.
Этот параметр позволяет указать, в каком типе коллекции должно храниться значение. Давайте посмотрим, как это можно сделать:
Observable<Book> bookObservable = Observable.just(
new Book("The North Water", 2016),
new Book("Origin", 2017),
new Book("Sleeping Beauties", 2017)
);
TestSubscriber<Map> 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, и, конечно же, простые примеры того, как их использовать.
Как всегда, все примеры кода в этой статье можно найти на Github .