Когда вы используете карту против flatMap в RxJava? - программирование

Когда вы используете карту против flatMap в RxJava?

Когда вы используете map vs flatMap в RxJava?

Скажем, например, мы хотим отобразить файлы, содержащие JSON, в строки, содержащие JSON -

Используя карту, мы должны как-то разобраться с Исключением. Но как?:

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // So Exception. What to do ?
        }
        return null; // Not good :(
    }
});

Используя flatMap, он гораздо более подробный, но мы можем перенаправить проблему по цепочке Observables и обработать ошибку, если мы выберем где-то еще и даже повторим:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override public void call(Subscriber<? super String> subscriber) {
                try {
                    String json = new Gson().toJson(new FileReader(file), Object.class);

                    subscriber.onNext(json);
                    subscriber.onCompleted();
                } catch (FileNotFoundException e) {
                    subscriber.onError(e);
                }
            }
        });
    }
});

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

4b9b3361

Ответ 1

map преобразовать одно событие в другое. flatMap преобразует одно событие в ноль или более. (это взято из IntroToRx)

Как вы хотите преобразовать json в объект, использование карты должно быть достаточно.

Работа с FileNotFoundException - еще одна проблема (использование карты или карты не поможет решить эту проблему).

Чтобы решить вашу проблему с Exception, просто поставьте ее с помощью исключения Non checked: RX вызовет обработчик onError для вас.

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // this exception is a part of rx-java
            throw OnErrorThrowable.addValueAsLastCause(e, file);
        }
    }
});

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

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            // this static method is a part of rx-java. It will return an exception which is associated to the value.
            throw OnErrorThrowable.addValueAsLastCause(e, file);
            // alternatively, you can return Obersable.empty(); instead of throwing exception
        }
    }
});

Вы также можете вернуть в версии flatMap новый Observable, который является просто ошибкой.

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
        }
    }
});

Ответ 2

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

В практическом смысле Map только делает трансформацию над цепным ответом (не возвращая Observable); в то время как FlatMap возвращает Observable<T>, поэтому FlatMap рекомендуется, если вы планируете асинхронный вызов внутри метода, So:

  • Карта возвращает объект типа T
  • FlatMap возвращает Observable<T>.

Яркий пример можно увидеть здесь: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk.

Couchbase Java 2.X Client использует Rx для обеспечения асинхронных вызовов удобным способом. Поскольку он использует Rx, он имеет карту методов и FlatMap, объяснение в их документации может быть полезно для понимания общей концепции.

Чтобы обрабатывать ошибки, переопределите onError на вашем susbcriber.

Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) { }
};

Это может помочь просмотреть этот документ: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Хороший источник информации о том, как управлять ошибками с помощью RX, можно найти по адресу: https://gist.github.com/daschl/db9fcc9d2b932115b679

Ответ 3

В вашем случае я думаю, что вам нужна карта, так как есть только 1 вход и 1 выход.

Функция, предоставляемая картами, просто принимает элемент и возвращает элемент, который будет удален (только один раз).

Функция flatMap - функция принимает элемент, а затем возвращает "Наблюдаемый", то есть каждый элемент нового "Наблюдаемого" будет издаваться отдельно дальше.

Может быть, код поможет вам разобраться.

        //START DIFFERENCE BETWEEN MAP AND FLATMAP
    Observable.just("item1")
            .map( str -> {
                System.out.println("inside the map " + str);
                return str;
            })
            .subscribe(System.out::println);

    Observable.just("item2")
            .flatMap( str -> {
                System.out.println("inside the flatMap " + str);
                return Observable.just(str + "+", str + "++" , str + "+++");
            })
            .subscribe(System.out::println);
    //END DIFFERENCE BETWEEN MAP AND FLATMAP

Вывод:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++

Ответ 4

Как я думаю об этом, вы используете flatMap, когда функция, которую вы хотите поместить внутри map(), возвращает Observable. В этом случае вы все равно можете использовать map(), но это было бы непрактично. Позвольте мне объяснить, почему.

Если в таком случае вы решили придерживаться map, вы получите Observable<Observable<Something>>. Например, в вашем случае, если мы использовали мнимую библиотеку RxGson, которая вернула метод Observable<String> из него toJson() (вместо простого возврата String), он будет выглядеть так:

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}); // you get Observable<Observable<String>> here

В этот момент было бы довольно сложно найти subscribe() для такого наблюдаемого. Внутри него вы получите Observable<String>, которому вам снова понадобится subscribe(), чтобы получить значение. Это не практично или приятно смотреть.

Итак, чтобы сделать это полезным, идея состоит в том, чтобы "сгладить" это наблюдаемое наблюдаемое (вы можете начать видеть, откуда приходит имя _flat_Map). RxJava предоставляет несколько способов сгладить наблюдаемые данные и для простоты позволяет предположить merge. Слияние в основном берет кучу наблюдаемых и испускает всякий раз, когда кто-либо из них испускает. (Многие люди будут утверждать, что switch будет лучшим дефолтом. Но если вы испускаете только одно значение, это не имеет значения.)

Итак, изменив наш предыдущий фрагмент, мы получим:

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}).merge(); // you get Observable<String> here

Это намного более полезно, потому что подписываясь на это (или сопоставление, или фильтрацию, или...), вы просто получаете значение String. (Также, разумеется, такой вариант merge() не существует в RxJava, но если вы понимаете идею слияния, я надеюсь, вы также поймете, как это будет работать.)

Итак, в основном потому, что такой merge() должен, вероятно, когда-либо быть полезным только тогда, когда ему удается вернуть наблюдаемое значение map(), поэтому вам не нужно вводить его снова и снова, flatMap() был создан как сокращенное. Он применяет функцию сопоставления так же, как и обычный map(), но позже вместо того, чтобы испускать возвращаемые значения, он также "сглаживает" (или объединяет) их.

Это общий прецедент. Это наиболее полезно в кодовой базе, которая использует Rx в любом месте, и у вас есть много методов, возвращающих наблюдаемые, которые вы хотите связать с другими методами, возвращающими наблюдаемые.

В вашем случае использования это также полезно, потому что map() может преобразовать только одно значение, испускаемое в onNext(), в другое значение, испускаемое в onNext(). Но он не может преобразовать его в несколько значений, никакого значения вообще или ошибки. И как akarnokd написал в своем ответе (и, разумеется, он гораздо умнее меня, возможно, вообще, но, по крайней мере, когда речь заходит о RxJava), вы не должны исключайте исключения из вашего map(). Поэтому вместо этого вы можете использовать flatMap() и

return Observable.just(value);

когда все идет хорошо, но

return Observable.error(exception);

когда что-то не получается.
См. Его ответ для полного фрагмента: fooobar.com/questions/49142/...

Ответ 5

Я просто хотел добавить, что с flatMap вам действительно не нужно использовать свой собственный пользовательский наблюдаемый внутри функции, и вы можете полагаться на стандартные методы/операторы factory:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        try {
            String json = new Gson().toJson(new FileReader(file), Object.class);
            return Observable.just(json);
        } catch (FileNotFoundException ex) {
            return Observable.<String>error(ex);
        }
    }
});

Как правило, вам следует избегать исключений (Runtime-) из методов onXXX и обратных вызовов, если это возможно, хотя мы поместили столько гарантий, сколько могли в RxJava.

Ответ 6

Вот просто правило большого пальца, которое я использую, чтобы помочь мне решить, как использовать flatMap() над map() в Rx Observable.

Как только вы придете к решению о том, что вы собираетесь использовать преобразование map, вы должны написать свой код преобразования, чтобы вернуть какой-либо объект права?

Если вы возвращаетесь в качестве конечного результата вашего преобразования:

  • не наблюдаемый объект, тогда вы должны использовать только map(). И map() обертывает этот объект в Observable и испускает его.

  • объект Observable, тогда вы должны использовать flatMap(). И flatMap() разворачивает Observable, выбирает возвращенный объект, обертывает его своим собственным Observable и испускает его.

Скажем, например, у нас есть метод titleCase (String inputParam), который возвращает объект Tamed Cased String входного параметра. Тип возврата этого метода может быть String или Observable<String>.

  • Если возвращаемый тип titleCase(..) должен быть простым String, вы должны использовать map(s -> titleCase(s))

  • Если тип возврата titleCase(..) должен быть Observable<String>, то вы должны использовать flatMap(s -> titleCase(s))

Надеюсь, что это прояснится.

Ответ 7

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

вы должны использовать Exceptions.propagate, который является оберткой, чтобы вы могли отправлять эти проверенные исключения в механизм rx.

Observable<String> obs = Observable.from(jsonFile).map(new Func1<File, String>() { 
@Override public String call(File file) {
    try { 
        return new Gson().toJson(new FileReader(file), Object.class);
    } catch (FileNotFoundException e) {
        throw Exceptions.propagate(t); /will propagate it as error
    } 
} 
});

Затем вы должны обработать эту ошибку в подписчике

obs.subscribe(new Subscriber<String>() {
    @Override 
    public void onNext(String s) { //valid result }

    @Override 
    public void onCompleted() { } 

    @Override 
    public void onError(Throwable e) { //e might be the FileNotFoundException you got }
};); 

Для него есть отличная почта: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

Ответ 8

В некоторых случаях у вас может быть цепочка наблюдаемых, в которой ваша наблюдаемая вернет другую наблюдаемую. "flatmap" тип разворота второй наблюдаемый, который похоронен в первом, и позволяет вам напрямую обращаться к данным, которые второй наблюдаемый выплевывает при подписке.