RxJava'da map vs flatMap ne zaman kullanılır?


180

Ne zaman kullanırım mapvs flatMapiçinde RxJava ?

Örneğin, JSON içeren Dosyaları JSON içeren Dizelerle eşlemek istiyoruz.

Kullanarak map, bir Exceptionşekilde başa çıkmak zorundayız . Ama nasıl?:

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

Kullanarak flatMap, çok daha ayrıntılı, ancak sorunu Observablesbaşka bir yerde seçip tekrar denersek zinciri zincirine iletebilir ve hatayı halledebiliriz:

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

Ben sadeliği seviyorum map, ama hata işleme flatmap(ayrıntı değil). Bunun etrafında yüzen en iyi uygulamaları görmedim ve bunun pratikte nasıl kullanıldığını merak ediyorum.

Yanıtlar:


121

mapbir olayı diğerine dönüştürür. flatMapbir etkinliği sıfıra veya daha fazla etkinliğe dönüştürün. (bu IntroToRx'ten alınmıştır )

Json'unuzu bir nesneye dönüştürmek istediğiniz için, haritayı kullanmak yeterli olmalıdır.

FileNotFoundException ile ilgilenmek başka bir sorundur (map veya flatmap kullanmak bu sorunu çözmez).

İstisna sorununuzu çözmek için, sadece Kontrol edilmemiş bir istisna ile atın: RX sizin için onError işleyicisini arayacaktır.

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 ile aynı sürüm:

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

Siz de flatMap sürümünde yeni bir Gözlemlenebilir hatası olan bir hata döndürebilirsiniz.

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
Bu, subscriber.onError()vb. Çağırmaz. Gördüğüm tüm örnekler hataları bu şekilde yönlendirdi. Bu önemli değil mi?
Christopher Perry

7
Ait kurucular unutmayın OnErrorThrowablevardır privateve kullanmak gerek OnErrorThrowable.from(e)yerine.
david.mihola

Yeni güncelledim. OnErrorThrowable.from (e) değeri tutmaz, bu yüzden değeri tutması gereken OnErrorThrowable.addValueAsLastCause (e, dosya) kullanırım.
dwursteisen

1
Kod örneklerini beğendim, ancak flatMap çağrılarının imzasını yalnızca String yerine Observable <String> döndürmek için güncellerseniz yardımcı olur çünkü teknik olarak ikisi arasındaki fark değil mi?
Zengin Ehmer

78

FlatMap, haritaya çok benzer şekilde davranır, fark, uyguladığı işlevin gözlemlenebilir bir kendisini döndürmesi, bu nedenle eşzamansız işlemler üzerinde eşleştirmek için mükemmeldir.

Pratik anlamda, Harita fonksiyonu sadece zincirleme tepki üzerinde bir dönüşüm yapar (bir Gözlenebilir döndürmez); FlatMap uygulandığında işlev bir döndürür Observable<T>, bu nedenle yöntemin içinde eşzamansız bir arama yapmayı planlıyorsanız FlatMap önerilir.

Özet:

  • Harita T türünde bir nesne döndürür
  • FlatMap bir Gözlemlenebilir döndürür.

Net bir örnek burada görülebilir: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

Couchbase Java 2.X Client, eşzamansız çağrıları uygun bir şekilde sağlamak için Rx'i kullanır. Rx kullandığından, harita haritasına ve FlatMap'e sahip olduğundan, belgelerindeki açıklama genel kavramı anlamaya yardımcı olabilir.

Hataları işlemek için susbcriber'ınızdaki onError değerini geçersiz kılın.

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

Bu belgeye bakmak yardımcı olabilir: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

RX ile hataların nasıl yönetileceği hakkında iyi bir kaynak şu adreste bulunabilir: https://gist.github.com/daschl/db9fcc9d2b932115b679


Özet yanlış. Harita ve FlatMap aynı tür döndürür ancak uyguladıkları işlev farklı tür döndürür.
CoXier

61

Durumunuzda haritaya ihtiyacınız var, çünkü sadece 1 giriş ve 1 çıkış var.

harita tarafından sağlanan işlev bir öğeyi kabul eder ve aşağıya (yalnızca bir kez) yayılacak bir öğeyi döndürür.

flatMap tarafından sağlanan işlev bir öğeyi kabul eder ve ardından bir "Gözlenebilir" döndürür, yani yeni "Gözlenebilir" öğesinin her bir öğesi ayrı ayrı yayılır.

Kod sizin için işleri temizler:

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

Çıktı:

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

Bir harita kullanmanın en iyi fikir olup olmadığından emin değilim. FileReader'ın eşzamansız bir çağrı olacağını varsayalım. Ardından haritayı flatMap olarak değiştirmeniz gerekir. Bir harita olarak bırakmak, olayların beklendiği gibi tetiklenmeyeceği anlamına gelir ve karışıklığa neden olur. Ben hala RX Java öğreniyorum gibi birkaç kez ısırıldı. FlatMap'in işlerin beklediğiniz gibi işlenmesini sağlamanın kesin bir yolu olduğunu düşünüyorum.
user924272

24

Bu konuda düşündüğüm yol, flatMapiçine koymak istediğiniz işlev bir an map()döndürdüğünüzde kullanmanızdır Observable. Bu durumda hala kullanmaya çalışabilirsiniz, map()ancak pratik olmaz. Nedenini açıklamaya çalışayım.

Böyle bir durumda kalmaya karar mapverirseniz, bir Observable<Observable<Something>>. Biz hayali RxGson kütüphanesi kullanıldığı takdirde durumunda Örneğin, bu bir iade Observable<String>'s dan toJson()yöntemi (yerine sadece dönen String) bu şekilde görünecektir:

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

Bu noktada subscribe()böyle bir gözlemlenebilir için oldukça zor olurdu . İçinde bir değer almak için Observable<String>tekrar gereken bir alacaktı subscribe(). Hangi pratik ya da bakmak hoş değil.

Bu nedenle, bir fikri kullanışlı yapmak gözlemlenebilir olan bu gözlemlenebilirleri "düzleştirmek" tir (_flat_Map adının nereden geldiğini görmeye başlayabilirsiniz). RxJava, gözlemlenebilirleri düzleştirmek için birkaç yol sağlar ve basitlik için birleştirmenin istediğimiz şey olduğunu varsayalım . Birleştirme temel olarak bir sürü gözlemlenebilir alır ve herhangi bir ses yaydığında yayılır. (Birçok kişi, geçişin daha iyi bir varsayılan olacağını savunur . Ancak yalnızca bir değer yayarsanız, bunun bir önemi yok.)

Bu yüzden önceki kod parçamızı değiştirdiğimizde:

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

Bu çok daha kullanışlıdır, çünkü buna abone olmak (veya haritalama veya filtreleme veya ...) sadece Stringdeğeri elde edersiniz . (Ayrıca, merge()RxJava'da böyle bir varyantın olmadığını unutmayın, ancak birleştirme fikrini anlarsanız, umarım bunun nasıl çalışacağını da anlarsınız.)

Yani temelde böyle bir şey ancak gözlemlenebilir bir dönüşü merge()başardığında muhtemelen yararlı olmalı map()ve bu yüzden bunu tekrar tekrar yazmak zorunda değilsiniz, flatMap()stenografi olarak yaratıldı. Eşleme işlevini normal bir şekilde uygular map(), ancak daha sonra döndürülen değerleri yaymak yerine bunları "düzleştirir" (veya birleştirir).

Genel kullanım durumu budur. En çok Rx kullanan bir kod tabanında yer allover ve gözlemlenebilir döndüren birçok yöntem var, gözlemlenebilir döndüren diğer yöntemlerle zincirlemek istediğiniz.

Kullanım durumunuzda da yararlı olur, çünkü map()sadece yayılan bir değeri yayılan onNext()başka bir değere dönüştürebilir onNext(). Ancak bunu birden çok değere dönüştüremez, hiç değer veya hata olmaz. Ve akarnokd'un cevabında yazdığı gibi (ve genel olarak benden çok daha akıllı olduğuna dikkat edin, muhtemelen genel olarak, ama en azından RxJava söz konusu olduğunda), sizin durumunuzdan istisnalar atmamalısınız map(). Bunun yerine flatMap()ve

return Observable.just(value);

her şey yolunda gittiğinde, ama

return Observable.error(exception);

bir şey başarısız olduğunda.
Tam bir pasaj için cevabına bakın: https://stackoverflow.com/a/30330772/1402641


1
bu benim tercih ettiğim cevap. temel olarak, gözlemlenebilir bir IN'yi gözlemlenebilir bir IF'in iç içe yerleştirmesiyle sonuçlanırsınız.
filthy_wizard

21

Soru RxJava'da map vs flatMap'i ne zaman kullanıyorsunuz? . Ve basit bir demonun daha spesifik olduğunu düşünüyorum.

Yayılan öğeyi başka bir türe dönüştürmek istediğinizde, dosyayı String'e dönüştürmeniz durumunda, map ve flatMap her ikisi de çalışabilir. Ama harita operatörünü tercih ediyorum çünkü daha açık.

Ancak bir yerde flatMapsihirli işler mapyapabilir ama yapamazlar. Örneğin, bir kullanıcının bilgi almak istiyorum ama ilk kullanıcı oturum açtığında onun kimliğini almak zorunda. Açıkçası ben iki istek gerekiyor ve onlar sırayla.

Hadi başlayalım.

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

Observable<UserInfo> fetchUserInfo(String userId);

Burada, biri giriş için, diğeri Responsekullanıcı bilgilerini almak için olmak üzere iki yöntem bulunmaktadır .

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

Gördüğünüz gibi, flatMap fonksiyonunda, önce kullanıcı kimliğini Responsealıp kullanıcı bilgisini alıyorum . İki istek tamamlandığında, kullanıcı arayüzünü güncelleme veya verileri veritabanına kaydetme gibi işimizi yapabiliriz.

Ancak kullanırsanız mapböyle güzel bir kod yazamazsınız. Tek kelimeyle, flatMapistekleri serileştirmemize yardımcı olabilir.


18

İşte basit bir başparmak kuralı bana ne zaman kullanılacağına olarak karar yardıma kullandıkları flatMap()üzerinde map()Rx en içinde Observable.

Bir mapdönüşüm uygulayacağınıza karar verdiğinizde , bazı Object'i döndürmek için dönüşüm kodunuzu yazacaksınız değil mi?

Dönüşümünüzün sonucu olarak iade ettiğiniz şey:

  • gözlemlenebilir olmayan bir nesne kullanırsınızmap() . Ve map()o nesneyi bir Gözlenebilir olana sarar ve yayar.

  • bir Observablenesne, o zaman kullanırsınızflatMap() . Ve flatMap()Gözlenebilir'i açar, döndürülen nesneyi alır, kendi Gözlenebilir ile sarar ve yayar.

Diyelim ki, giriş parametresinin Başlıklı Cased String nesnesini döndüren titleCase (String inputParam) yöntemimiz var. Bu yöntemin dönüş türü Stringveya olabilir Observable<String>.

  • Dönüş türü titleCase(..)yalnızca Stringolsaydı,map(s -> titleCase(s))

  • Dönüş türü ise titleCase(..)olacaktı Observable<String>, ardından kullanmak istiyorumflatMap(s -> titleCase(s))

Umarım bu açıklığa kavuşur.


11

Sadece şunu eklemek istedim flatMap, gerçekten kendi özel Gözlemlenebilir işlevini kullanmanıza gerek yok ve standart fabrika yöntemlerine / operatörlerine güvenebilirsiniz:

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

Genellikle, RXXJava'da mümkün olduğunca çok sayıda güvenlik önlemi alsak da, onXXX yöntemlerinden ve geri çağrılardan (Runtime-) istisnaları atmaktan kaçınmalısınız.


Ama haritanın yeterli olduğunu düşünüyorum. Yani flatMap ve harita bir alışkanlık değil mi?
CoXier

6

Bu senaryoda haritayı kullanın, bunun için yeni bir Gözlenebilir'e ihtiyacınız yoktur.

işaretli istisnaları rx mekanizmasına gönderebilmeniz için bir sarıcı olan Exceptions.propagate komutunu kullanmalısınız

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

Daha sonra abonede bu hatayı ele almalısınız

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

Bunun için mükemmel bir yazı var: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/


0

Bazı durumlarda, gözlemlenebilir zinciriniz olabilir, burada gözlemlenebilir bir başka gözlemlenebilir geri döner. 'flatmap' tür, birincisinde gömülü olan ikinci gözlemlenebilir olanı açar ve abone olurken gözlemlenebilir olan ikinci verilere doğrudan erişmenizi sağlar.


0

Flatmap gözlemlenebilirleri gözlemlenebilirlerle eşleştirir. Öğeleri öğelerle eşleştirir.

Flatmap daha esnektir, ancak Harita daha hafif ve doğrudantır, bu nedenle kullanımınıza bağlıdır.

HERHANGİ BİR zaman uyumsuzluğu (anahtarlama iş parçacıkları dahil) yapıyorsanız, Harita tüketicinin imha edilip edilmediğini (hafifliğin bir parçası) kontrol etmeyeceğinden Flatmap'i kullanmalısınız.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.