คุณใช้ map vs flatMap เมื่อใดใน RxJava


180

เมื่อไหร่ที่คุณใช้mapVS flatMapในRxJava ?

ตัวอย่างเช่นเราต้องการจับคู่ไฟล์ที่มี JSON เป็น Strings ที่มี 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มัน verbose มากขึ้น แต่เราสามารถส่งต่อปัญหาลงโซ่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(ไม่ใช่ verbosity) ฉันไม่ได้เห็นแนวปฏิบัติที่ดีที่สุดเกี่ยวกับเรื่องนี้ลอยไปมาและฉันอยากรู้ว่ามันถูกใช้ในทางปฏิบัติอย่างไร

คำตอบ:


121

mapแปลงเหตุการณ์หนึ่งเป็นเหตุการณ์อื่น flatMapแปลงเหตุการณ์หนึ่งเหตุการณ์ให้เป็นศูนย์หรือมากกว่านั้น (สิ่งนี้นำมาจากIntroToRx )

ตามที่คุณต้องการแปลง json ของคุณเป็นวัตถุการใช้แผนที่ควรจะเพียงพอ

การจัดการกับ FileNotFoundException เป็นปัญหาอื่น (การใช้แผนที่หรือ flatmap จะไม่แก้ปัญหานี้)

ในการแก้ปัญหาข้อยกเว้นเพียงแค่โยนข้อยกเว้นที่ไม่ได้ตรวจสอบ: 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));
        }
    }
});

2
สิ่งนี้ไม่ได้โทรsubscriber.onError()เป็นต้นตัวอย่างทั้งหมดที่ฉันเห็นมีข้อผิดพลาดที่ส่งไปในทางนั้น มันไม่สำคัญหรอก
Christopher Perry

7
โปรดทราบว่าตัวสร้างของOnErrorThrowableอยู่privateและคุณจำเป็นต้องใช้OnErrorThrowable.from(e)แทน
david.mihola

ฉันเพิ่งปรับปรุง OnErrorThrowable.from (e) ไม่เก็บค่าดังนั้นฉันใช้ OnErrorThrowable.addValueAsLastCause (e, file) แทนซึ่งควรเก็บค่าไว้
dwursteisen

1
ฉันชอบตัวอย่างรหัส แต่มันจะช่วยได้ถ้าคุณอัปเดตลายเซ็นของการเรียก flatMap เพื่อส่งกลับ Observable <String> แทนที่จะเป็นแค่ String ... เพราะเทคนิคนั้นไม่แตกต่างกันหรือไม่
คนรวย Ehmer

78

FlatMap ทำงานเหมือนแผนที่มากความแตกต่างคือฟังก์ชั่นที่ใช้จะส่งกลับค่าที่สังเกตได้ดังนั้นจึงเหมาะอย่างยิ่งในการแมปผ่านการดำเนินการแบบอะซิงโครนัส

ในทางปฏิบัติฟังก์ชั่นแผนที่ใช้เพียงแค่ทำให้การเปลี่ยนแปลงมากกว่าการตอบสนองที่ถูกล่ามโซ่ (ไม่กลับสังเกต); ในขณะที่ฟังก์ชั่น FlatMap ใช้ผลตอบแทนObservable<T>นั่นคือเหตุผลที่แนะนำ FlatMap ถ้าคุณวางแผนที่จะทำการโทรแบบอะซิงโครนัสภายในวิธีการ

สรุป:

  • แผนที่ส่งคืนวัตถุประเภท T
  • FlatMap ส่งคืน Observable

ตัวอย่างที่ชัดเจนสามารถมองเห็นได้ที่นี่: 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


สรุปเป็นสิ่งที่ผิด 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+++

ไม่แน่ใจว่าการใช้แผนที่เป็นความคิดที่ดีที่สุดหรือไม่ สมมติว่า FileReader นั้นเป็นการเรียกแบบอะซิงโครนัส ถ้าอย่างนั้นคุณต้องเปลี่ยนแผนที่เป็น flatMap การปล่อยให้เป็นแผนที่จะหมายความว่าคุณจะไม่ถูกไล่ออกจากเหตุการณ์อย่างที่คาดไว้และจะทำให้เกิดความสับสน ฉันถูกกัดครั้งนี้เพราะฉันยังเรียนรู้ RX Java ฉันพบว่า flatMap เป็นวิธีที่มั่นใจได้ว่าทุกอย่างจะได้รับการดำเนินการตามที่คุณคาดหวัง
user924272

24

วิธีที่ฉันคิดเกี่ยวกับมันคือคุณใช้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 ให้สองสามวิธีในการแผ่สิ่งที่สังเกตได้และเพื่อความเรียบง่ายสมมติว่าการรวมเป็นสิ่งที่เราต้องการ โดยทั่วไปแล้วจะมีกลุ่มของสิ่งที่สังเกตได้และปล่อยออกมาเมื่อใดก็ตาม (ผู้คนจำนวนมากจะเถียงเปลี่ยนจะเป็นค่าเริ่มต้นที่ดีกว่า แต่ถ้าคุณเปล่งเพียงค่าเดียวมันก็ไม่สำคัญเลย)

ดังนั้นการแก้ไขข้อมูลโค้ดก่อนหน้าของเราเราจะได้รับ:

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()แต่ต่อมาแทนที่จะปล่อยค่าที่ส่งคืนมันก็ "แบน" (หรือผสาน) พวกเขา

เป็นกรณีการใช้งานทั่วไป มันมีประโยชน์มากที่สุดใน codebase ที่ใช้ Rx allover สถานที่และคุณมีวิธีส่งคืน observables หลายวิธีซึ่งคุณต้องการเชื่อมโยงกับวิธีอื่น ๆ ที่ส่งคืน observables

ในกรณีที่คุณใช้งานก็มีประโยชน์เช่นกันเพราะmap()สามารถแปลงค่าที่ปล่อยออกมาonNext()เป็นค่าอื่นที่ปล่อยออกมาonNext()เท่านั้น แต่ไม่สามารถแปลงเป็นหลายค่าไม่มีค่าเลยหรือมีข้อผิดพลาด และเป็นakarnokdเขียนไว้ในคำตอบของเขา (และใจคุณเขามากอย่างชาญฉลาดกว่าผมอาจจะอยู่ในทั่วไป แต่อย่างน้อยเมื่อมันมาถึง RxJava) map()ที่คุณไม่ควรโยนข้อยกเว้นจากคุณ ดังนั้นคุณสามารถใช้flatMap()และ

return Observable.just(value);

เมื่อทุกอย่างไปได้ดี แต่

return Observable.error(exception);

เมื่อสิ่งที่ล้มเหลว
ดูคำตอบของเขาสำหรับตัวอย่างที่สมบูรณ์: https://stackoverflow.com/a/30330772/1402641


1
นี่คือคำตอบที่ฉันโปรดปราน โดยทั่วไปคุณจะทำรังสิ่งที่สังเกตได้ใน IF ที่สังเกตได้ซึ่งเป็นวิธีการของคุณที่ส่งคืน
filthy_wizard

21

คำถามคือเมื่อไหร่ที่คุณใช้ map vs flatMap ใน RxJava . และฉันคิดว่าตัวอย่างง่าย ๆ มีความเฉพาะเจาะจงมากกว่า

เมื่อคุณต้องการแปลงไอเท็มที่ปล่อยออกมาเป็นประเภทอื่นในกรณีของคุณที่แปลงไฟล์เป็น String แม็พและ 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นั้นดึงข้อมูลผู้ใช้ เมื่อคำขอสองคำขอเสร็จสิ้นเราสามารถทำงานของเราเช่นอัปเดต UI หรือบันทึกข้อมูลลงในฐานข้อมูล

อย่างไรก็ตามหากคุณใช้mapคุณไม่สามารถเขียนรหัสที่ดีเช่นนี้ คำหนึ่งflatMapสามารถช่วยเราเรียงลำดับคำขอ


18

นี่เป็นเรื่องง่ายที่นิ้วหัวแม่มือกฎที่ฉันใช้ความช่วยเหลือฉันตัดสินใจเป็นเวลาที่จะใช้flatMap()มากกว่าmap()ใน ObservableRx

เมื่อคุณตัดสินใจแล้วว่าจะต้องใช้การmapแปลงคุณจะต้องเขียนโค้ดการแปลงของคุณเพื่อคืนค่า Object

หากสิ่งที่คุณจะกลับมาเป็นผลสุดท้ายของการเปลี่ยนแปลงของคุณคือ:

  • map()ไม่ใช่วัตถุที่สังเกตแล้วคุณต้องการใช้เพียง และmap()ล้อมวัตถุนั้นใน Observable และปล่อยออกมา

  • ObservableflatMap()วัตถุแล้วคุณต้องการใช้ และflatMap()ยกเลิกการ Observable เลือกวัตถุที่ถูกคืนแล้วล้อมด้วย Observable ของมันเองและปล่อยมันออกมา

ยกตัวอย่างเช่นเราได้ใช้เมธอด titleCase (String inputParam) ที่ส่งคืนออบเจ็กต์ Titled Cased String ของพารามิเตอร์อินพุต ประเภทการกลับมาของวิธีการนี้สามารถหรือStringObservable<String>

  • หากประเภทการคืนสินค้าtitleCase(..)เป็นเพียงStringคุณต้องการใช้map(s -> titleCase(s))

  • หากประเภทการกลับมาของtitleCase(..)เขาจะเป็นObservable<String>แล้วคุณต้องการใช้flatMap(s -> titleCase(s))

หวังว่าชัดเจน


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


แต่ฉันคิดว่าแผนที่ก็เพียงพอแล้ว ดังนั้นแผนที่และแผนที่แบนจึงเป็นนิสัยใช่ไหม
CoXier

6

ในสถานการณ์นั้นใช้แผนที่คุณไม่จำเป็นต้องมี Observable แบบใหม่

คุณควรใช้ Exceptions.propagate ซึ่งเป็น wrapper เพื่อให้คุณสามารถส่งข้อยกเว้นที่เลือกไปยังกลไก 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

ในบางกรณีคุณอาจมีห่วงโซ่ของสิ่งที่สังเกตได้ซึ่งในสิ่งที่คุณสังเกตได้จะกลับมาเป็นสิ่งที่สังเกตได้อีก ชนิด 'แบนแมป' ที่ไม่ได้แยกส่วนที่สังเกตได้ซึ่งถูกฝังอยู่ในอันแรกและให้คุณเข้าถึงข้อมูลที่สองที่สังเกตได้โดยตรงจะถูกแยกออกในขณะที่สมัครรับข้อมูล


0

แมป Flatmap ที่สามารถสังเกตได้เพื่อสังเกตได้ แม็พไอเท็มกับไอเท็ม

Flatmap นั้นมีความยืดหยุ่นมากกว่า แต่ Map นั้นมีน้ำหนักเบาและตรงกว่าดังนั้นมันขึ้นอยู่กับการใช้งานของคุณ

หากคุณกำลังทำอะไรแบบซิงค์ (รวมถึงการสลับเธรด) คุณควรใช้ Flatmap เนื่องจาก Map จะไม่ตรวจสอบว่าผู้ใช้บริการถูกกำจัดหรือไม่ (ส่วนหนึ่งของน้ำหนักเบา)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.