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

180

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

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

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

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);
                }
            }
        });
    }
});

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

Кристофер Перри
источник

Ответы:

121

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

Поскольку вы хотите преобразовать свой json в объект, достаточно использовать map.

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

Чтобы решить проблему исключений, просто добавьте не проверенное исключение: 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);
        }
    }
});

точно такая же версия с flatmap:

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));
        }
    }
});
dwursteisen
источник
2
Это не вызывает subscriber.onError()и т. Д. Все примеры, которые я видел, направляли ошибки таким образом. Это не имеет значения?
Кристофер Перри
7
Обратите внимание, что конструкторы OnErrorThrowableявляются privateи вам нужно использовать OnErrorThrowable.from(e)вместо этого.
Давид.михола
Я только что обновил. OnErrorThrowable.from (e) не сохраняет значение, поэтому вместо него я использую OnErrorThrowable.addValueAsLastCause (e, file), который должен сохранять значение.
dwursteisen
1
Мне нравятся примеры кода, но было бы полезно, если бы вы обновили сигнатуру вызовов flatMap, чтобы она возвращала Observable <String> вместо просто String ... потому что это технически не является разницей между ними?
Rich Ehmer
78

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

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

Резюме:

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

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

Клиент Couchbase Java 2.X использует Rx для предоставления асинхронных вызовов удобным способом. Поскольку он использует Rx, у него есть методы map и 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.

1vand1ng0
источник
Резюме неверно. Map и FlatMap возвращают один и тот же тип, но функция, которую они применяют, возвращает другой тип.
CoXier
61

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

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

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

Может быть, код прояснит для вас вещи:

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);

Вывод:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
mt.uulu
источник
Не уверен, что использование карты - лучшая идея, хотя она будет работать. Предположим, FileReader должен был стать асинхронным вызовом. Тогда вам нужно изменить карту на flatMap. Если вы оставите его в виде карты, это приведет к тому, что вы не запустите события, как ожидалось, и вызовете путаницу. Я был укушен этим несколько раз, поскольку я все еще изучаю RX Java. Я считаю, что flatMap - верный способ гарантировать, что все обрабатывается так, как вы ожидаете.
user924272
24

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

Если в таком случае вы решили придерживаться map, вы получите Observable<Observable<Something>>. Например, в вашем случае, если бы мы использовали воображаемую библиотеку RxGson, которая возвращала метод Observable<String>из своего toJson()метода (вместо простого возврата a 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 предоставляет несколько способов сгладить наблюдаемые объекты и для простоты предположим, что объединение - это то, что мы хотим. Слияние в основном берет кучу наблюдаемых и испускает всякий раз, когда любой из них излучает. (Многие люди утверждают, что переключение будет лучшим вариантом по умолчанию. Но если вы используете только одно значение, это не имеет значения.)

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

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);

когда что-то не получается.
Смотрите его ответ для полного фрагмента: https://stackoverflow.com/a/30330772/1402641

Марчин Козиньски
источник
1
это мой любимый ответ. в основном вы в конечном итоге вкладываете наблюдаемое IN в наблюдаемое IF, которое возвращает ваш метод.
filthy_wizard
21

Вопрос в том, когда вы используете карту против flatMap в RxJava? , И я думаю, что простая демонстрация более конкретна.

Если вы хотите преобразовать испускаемый элемент в другой тип, в вашем случае конвертация файла в String может сработать как map, так и flatMap. Но я предпочитаю оператор карты, потому что это более понятно.

Однако в каком-то месте, flatMapможет делать магическую работу, но mapне может. Например, я хочу получить информацию о пользователе, но я должен сначала получить его идентификатор при входе пользователя в систему. Очевидно, мне нужны два запроса, и они в порядке.

Давай начнем.

Observable<LoginResponse> login(String email, String password);

Observable<UserInfo> fetchUserInfo(String userId);

Вот два метода: один для входа в систему Response, а другой для получения информации о пользователе.

login(email, password)
        .flatMap(response ->
                fetchUserInfo(response.id))
        .subscribe(userInfo -> {
            // get user info and you update ui now
        });

Как видите, в функции применяется flatMap, сначала я получаю идентификатор пользователя, а Responseзатем извлекаю информацию о пользователе. Когда два запроса завершены, мы можем выполнить нашу работу, например обновить пользовательский интерфейс или сохранить данные в базе данных.

Однако, если вы используете, mapвы не можете написать такой хороший код. Одним словом, flatMapможет помочь нам сериализовать запросы.

CoXier
источник
18

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

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

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

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

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

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

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

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

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

karthiks
источник
11

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

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 столько защитных средств, сколько могли бы.

akarnokd
источник
Но я думаю, что карты достаточно. Так что flatMap и карта - это привычка, верно?
CoXier
6

В этом сценарии используйте карту, вам не нужен новый 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/

ndori
источник
0

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

Anoop Исаак
источник
0

Flatmap отображает наблюдаемые в наблюдаемые. Карта сопоставляет элементы с элементами.

Flatmap более гибкая, но Map более легкая и прямая, поэтому она зависит от вашего варианта использования.

Если вы делаете НИЧЕГО асинхронного (включая переключение потоков), вы должны использовать Flatmap, так как Map не будет проверять, находится ли потребитель (часть облегченного доступа)

skr1p7k1dd
источник