RxJava для Java: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

RxJava для Java: полное руководство по реактивному программированию

1. Что это и зачем нужна

RxJava — это библиотека для реактивного программирования на языке Java, реализующая концепцию Observable (наблюдаемого) и Observer (наблюдателя). Она позволяет обрабатывать асинхронные потоки данных и события с помощью композабельных (composable) операторов. В отличие от традиционных callback-ов или Future/Promise, RxJava предоставляет мощный набор инструментов для трансформации, фильтрации и комбинирования потоков данных, что делает код более читаемым, предсказуемым и менее подверженным ошибкам, связанным с многопоточностью.

Зачем нужна RxJava? В современной разработке Java-приложений (особенно Android, серверные приложения на Spring WebFlux, микросервисы) постоянно возникают задачи, связанные с асинхронностью: загрузка данных из сети, работа с базами данных, обработка пользовательских событий. RxJava решает эти задачи, предоставляя единый API для работы с любыми типами асинхронных последовательностей. Она позволяет избежать "callback hell" (ада из колбэков), упрощает обработку ошибок в асинхронных цепочках и предоставляет мощные средства для управления потоками (Schedulers).

Основные преимущества RxJava: декларативность (вы описываете, что нужно сделать, а не как), композабельность (операторы легко комбинируются), поддержка backpressure (противодавления) для управления скоростью потребления данных, и богатая экосистема (RxAndroid, RxJava2Extensions, RxRelay). Библиотека активно поддерживается и широко используется в индустрии, особенно в Android-разработке (Retrofit + RxJava, Room + RxJava).

2. Установка

RxJava распространяется через Maven Central. Для подключения к проекту добавьте зависимость в ваш файл build.gradle (для Gradle) или pom.xml (для Maven).

Gradle (Android / Java проект)

dependencies {
    implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
}

Maven

<dependency>
    <groupId>io.reactivex.rxjava3</groupId>
    <artifactId>rxjava</artifactId>
    <version>3.1.8</version>
</dependency>

Для Android-проектов часто добавляют также RxAndroid для работы с главным потоком:

implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'

3. Быстрый старт — минимальный рабочий пример

Создадим простой Observable, который генерирует строки "Hello" и "World", и подпишемся на него:

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;

public class RxJavaQuickStart {
    public static void main(String[] args) {
        // Создаем Observable, который испускает два элемента
        Observable<String> observable = Observable.just("Hello", "World");

        // Создаем Observer
        Observer<String> observer = new Observer<String>() {
            @Override
            public void onSubscribe(Disposable d) {
                System.out.println("Подписка установлена");
            }

            @Override
            public void onNext(String s) {
                System.out.println("Получено: " + s);
            }

            @Override
            public void onError(Throwable e) {
                System.err.println("Ошибка: " + e.getMessage());
            }

            @Override
            public void onComplete() {
                System.out.println("Поток завершен");
            }
        };

        // Подписываемся
        observable.subscribe(observer);
    }
}

Вывод:

Подписка установлена
Получено: Hello
Получено: World
Поток завершен

4. Основные методы/классы

4.1 Observable.just()

Сигнатура: public static <T> Observable<T> just(T item1, T item2, ...)

Что делает: Создает Observable, который испускает переданные элементы последовательно и затем завершается.

Observable<Integer> numbers = Observable.just(1, 2, 3, 4, 5);
numbers.subscribe(System.out::println);
// Вывод: 1 2 3 4 5

4.2 Observable.fromIterable()

Сигнатура: public static <T> Observable<T> fromIterable(Iterable<? extends T> iterable)

Что делает: Создает Observable из любого Iterable (List, Set, Queue и т.д.).

List<String> list = Arrays.asList("A", "B", "C");
Observable<String> observable = Observable.fromIterable(list);
observable.subscribe(System.out::println);
// Вывод: A B C

4.3 Observable.map()

Сигнатура: public final <R> Observable<R> map(Function<? super T, ? extends R> mapper)

Что делает: Преобразует каждый элемент потока с помощью переданной функции.

Observable<Integer> numbers = Observable.just(1, 2, 3);
Observable<String> mapped = numbers.map(n -> "Number: " + n);
mapped.subscribe(System.out::println);
// Вывод: Number: 1, Number: 2, Number: 3

4.4 Observable.filter()

Сигнатура: public final Observable<T> filter(Predicate<? super T> predicate)

Что делает: Фильтрует элементы, пропуская только те, которые удовлетворяют условию.

Observable<Integer> numbers = Observable.just(1, 2, 3, 4, 5, 6);
Observable<Integer> evenNumbers = numbers.filter(n -> n % 2 == 0);
evenNumbers.subscribe(System.out::println);
// Вывод: 2 4 6

4.5 Observable.flatMap()

Сигнатура: public final <R> Observable<R> flatMap(Function<? super T, ? extends ObservableSource<? extends R>> mapper)

Что делает: Преобразует каждый элемент в Observable и объединяет все полученные потоки в один (порядок не гарантируется).

Observable<String> words = Observable.just("Hello", "World");
Observable<String> letters = words.flatMap(word -> Observable.fromArray(word.split("")));
letters.subscribe(System.out::println);
// Вывод: H e l l o W o r l d (порядок может варьироваться)

4.6 Observable.concatMap()

Сигнатура: public final <R> Observable<R> concatMap(Function<? super T, ? extends ObservableSource<? extends R>> mapper)

Что делает: Аналогичен flatMap, но гарантирует сохранение порядка элементов (последовательное выполнение).

Observable<String> words = Observable.just("AB", "CD");
Observable<String> letters = words.concatMap(word -> Observable.fromArray(word.split("")));
letters.subscribe(System.out::println);
// Вывод: A B C D (всегда в правильном порядке)

4.7 Observable.zip()

Сигнатура: public static <T1, T2, R> Observable<R> zip(ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, BiFunction<? super T1, ? super T2, ? extends R> zipper)

Что делает: Объединяет элементы из двух (или более) Observable по индексу с помощью функции-комбинатора.

Observable<Integer> numbers = Observable.just(1, 2, 3);
Observable<String> letters = Observable.just("A", "B", "C");
Observable<String> zipped = Observable.zip(numbers, letters, (n, l) -> n + l);
zipped.subscribe(System.out::println);
// Вывод: 1A, 2B, 3C

4.8 Observable.merge()

Сигнатура: public static <T> Observable<T> merge(ObservableSource<? extends T> source1, ObservableSource<? extends T> source2)

Что делает: Объединяет несколько Observable в один, испуская элементы по мере их поступления (без гарантии порядка).

Observable<String> obs1 = Observable.just("A", "B");
Observable<String> obs2 = Observable.just("C", "D");
Observable<String> merged = Observable.merge(obs1, obs2);
merged.subscribe(System.out::println);
// Вывод: A B C D (порядок может быть любым)

4.9 Observable.subscribeOn() и Observable.observeOn()

Сигнатура: public final Observable<T> subscribeOn(Scheduler scheduler)
public final Observable<T> observeOn(Scheduler scheduler)

Что делает: subscribeOn определяет, на каком потоке будет выполняться Observable (создание данных). observeOn определяет, на каком потоке будут вызываться Observer (потребление данных).

import io.reactivex.rxjava3.schedulers.Schedulers;

Observable.just("Hello")
    .subscribeOn(Schedulers.io()) // Выполняем в IO-потоке
    .observeOn(Schedulers.single()) // Обрабатываем в single-потоке
    .subscribe(System.out::println);

4.10 Single, Maybe, Completable

Сигнатура: public abstract class Single<T>, public abstract class Maybe<T>, public abstract class Completable

Что делает: Специализированные типы Observable:
- Single — испускает ровно один элемент или ошибку.
- Maybe — испускает 0 или 1 элемент (или ошибку).
- Completable — только завершение или ошибка (без элементов).

// Single
Single.just("Result")
    .subscribe(System.out::println);

// Maybe
Maybe.empty()
    .subscribe(
        System.out::println,
        Throwable::printStackTrace,
        () -> System.out.println("Completed without value")
    );

// Completable
Completable.complete()
    .subscribe(() -> System.out.println("Done"));

5. Реальный практический пример

Пример: загрузка списка пользователей из API, их фильтрация по возрасту, преобразование в DTO и сохранение в базу данных с использованием RxJava.

import io.reactivex.rxjava3.core.*;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.util.*;
import java.util.concurrent.TimeUnit;

// Модели
class User {
    int id;
    String name;
    int age;
    User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

class UserDTO {
    String displayName;
    boolean isAdult;
    UserDTO(String displayName, boolean isAdult) {
        this.displayName = displayName;
        this.isAdult = isAdult;
    }
}

// Репозиторий (имитация)
class UserRepository {
    public Single<List<User>> fetchUsers() {
        return Single.fromCallable(() -> {
            // Имитация задержки сети
            Thread.sleep(100);
            return Arrays.asList(
                new User(1, "Alice", 25),
                new User(2, "Bob", 17),
                new User(3, "Charlie", 30),
                new User(4, "David", 15)
            );
        }).subscribeOn(Schedulers.io());
    }

    public Completable saveUser(UserDTO dto) {
        return Completable.fromAction(() -> {
            // Имитация записи в БД
            System.out.println("Сохранен: " + dto.displayName);
            Thread.sleep(50);
        }).subscribeOn(Schedulers.io());
    }
}

public class RealWorldExample {
    public static void main(String[] args) throws InterruptedException {
        UserRepository repository = new UserRepository();

        // Цепочка обработки
        Disposable disposable = repository.fetchUsers()
            .flatMapObservable(Observable::fromIterable) // Преобразуем List в поток
            .filter(user -> user.age >= 18) // Фильтруем совершеннолетних
            .map(user -> new UserDTO(
                user.name.toUpperCase(),
                user.age >= 18
            ))
            .flatMapCompletable(dto -> repository.saveUser(dto)) // Сохраняем каждого
            .andThen(Single.just("Все пользователи обработаны"))
            .subscribe(
                result -> System.out.println("Результат: " + result),
                error -> System.err.println("Ошибка: " + error.getMessage()),
                () -> System.out.println("Завершено")
            );

        // Ждем завершения (в реальном приложении используем CompositeDisposable)
        Thread.sleep(2000);
    }
}

Вывод:

Сохранен: ALICE
Сохранен: CHARLIE
Результат: Все пользователи обработаны
Завершено

6. Советы по производительности и типичные ошибки

Советы по производительности:

  1. Используйте правильный Scheduler:
    • Schedulers.io() — для I/O операций (сеть, файлы, БД).
    • Schedulers.computation()

Рекомендаций