Коли ви використовуєте карту vs flatMap в RxJava?


180

Коли ви використовуєте mapvs 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 в об’єкт, використання карти має бути достатньо.

Справа з 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);
        }
    }
});

точно таку ж версію з плоскою картою:

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.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
Це не вимагає subscriber.onError()і т. Д. Усі приклади, які я бачив, таким чином направляли помилки. Це не має значення?
Крістофер Перрі

7
Зауважте, що конструктори " OnErrorThrowableє" privateі їх потрібно використовувати OnErrorThrowable.from(e)замість цього.
david.mihola

Я щойно оновив. OnErrorThrowable.from (e) не зберігає значення, тому я використовую OnErrorThrowable.addValueAsLastCause (e, файл) замість цього, який повинен зберігати значення.
dwursteisen

1
Мені подобаються приклади коду, але це допомогло б, якщо ви оновили підпис викликів flatMap, щоб повернути Observable <String> замість просто String ... адже хіба це технічно не різниця між ними?
Річ Емер

78

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

У практичному сенсі функція Map, що застосовується, просто здійснює перетворення на ланцюгову відповідь (не повертаючи спостережуваного); в той час як функція FlatMap застосовує повернення Observable<T>, тому FlatMap рекомендується, якщо ви плануєте здійснювати асинхронний виклик всередині методу.

Підсумок:

  • Карта повертає об’єкт типу T
  • FlatMap повертає спостережуване.

Ясний приклад можна побачити тут: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

Couchbase Java 2.X Клієнт використовує Rx для забезпечення асинхронних дзвінків зручним способом. Оскільки він використовує Rx, має карту методів та FlatMap, пояснення в їхній документації може бути корисним для розуміння загальної концепції.

Щоб обробляти помилки, замініть onError на своєму підписнику.

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


Резюме неправильне. Map і FlatMap повертають одного типу, але функція, яку вони застосовують, повертає різний тип.
CoXier

61

У вашому випадку вам потрібна карта, оскільки є лише 1 вхід і 1 вихід.

функція map надається просто приймає елемент і повертає елемент, який буде випромінюватися далі (лише один раз) вниз.

Функція 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);

Вихід:

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

Не впевнений, чи найкраще використовувати карту, хоча це спрацювало б. Припустимо, 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
це моя відповідь. ви, в основному, вкладаєте спостережуване в спостережуване ІФ, і саме це повертає ваш метод.
filthy_wizard

21

Питання: Коли ви використовуєте карту vs 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може допомогти нам серіалізувати запити.


18

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

Після того, як ви приймете рішення, що ви будете використовувати mapтрансформацію, ви напишете свій код перетворення, щоб повернути деякий об'єкт право?

Якщо ви повертаєтесь як кінцевий результат трансформації:

  • об'єкт, який не спостерігається, тоді ви використовуєте простоmap() . І map()загортає цей об'єкт у спостережуване і випромінює його.

  • Observableоб'єкт, то ви будете використовуватиflatMap() . І flatMap()розгортає Спостережуване, підбирає повернутий об'єкт, загортає його власним Спостережним і випускає його.

Скажімо, наприклад, у нас є метод titleCase (String inputParam), який повертає титульний об'єкт Cased String вхідного парамуму. Тип повернення цього методу може бути Stringабо Observable<String>.

  • Якщо тип повернення titleCase(..)повинен був бути простим String, тоді ви використовуєтеmap(s -> titleCase(s))

  • Якби тип повернення titleCase(..)був Observable<String>, тоді ви користуєтесьflatMap(s -> titleCase(s))

Надія, яка прояснює.


11

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

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.


Але я думаю, що карти достатньо. Тож плоска карта та карта - це звичка, правда?
CoXier

6

У такому сценарії використання карти вам не потрібна нова Спостережна.

ви повинні використовувати 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/


0

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


0

Плоска карта відображає спостережувані до спостережуваних. Картографуйте об’єкти на елементи.

Flatmap є більш гнучким, але Map більш легким та прямим, тому він залежить від вашої справи.

Якщо ви робите будь-яку асинхронізацію (включаючи комутаційні нитки), вам слід скористатись Flatmap, оскільки Map не перевірятиме, чи не перебуває споживач (частина легкої ваги)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.