RxJava入门第八、九问(二)

开始第8个问题之前,先说一个词:变换,什么意思呢?

RxJava官方解释:所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。

如果你通过上面的定义没有很好地理解什么是变换、以及为什么要使用变换,那么请看下面这个故事:庖丁宰鸡(包学包会)

养鸡户(被观察者)发送了一个数据:

- 一只活蹦乱跳的鸡。

而这个养鸡户被很多观察者订阅了,这些观察者分别是:

- 烤鸡翅的夜宵店老板

- 做宫保鸡丁的厨子

- 做泡椒凤爪的厂家

他们虽然都能收到“这只活蹦乱跳的鸡”这个数据,但是他们分别只想要

- 鸡翅

- 鸡胸脯

- 鸡爪子

你给烤鸡翅的人一只活鸡,让他现场杀鸡取翅总归是不靠谱的吧。

所以需要一个屠户来进行“变换”,将一只活鸡,进行分割,输出鸡翅、鸡胸脯、鸡爪子给不同的观察者。

在这里操作符map、flatmap就是这个分割鸡肉的屠户,他们完成了“变换”。

注意:上面这个分割鸡的例子是在我写完后面的内容后,为了解释“变换”而临时加入的,后面的内容又重新说回了警察与小偷的故事,我懒得再改了,所以情节上显得有点跳跃。

8 我突然对“变换”很感兴趣,能说一下map操作符吗?

答:

RxJava有很多操作符,我们就从map操作符说起吧。

我们说回那个小偷吧

上文说到了小偷每行窃一次,就会把被窃者的体貌特征发送给警察,警察根据这个被窃者的体貌特征来定位,从而抓住了小偷。

上文中警察订阅了小偷,这次我们要换个观察者—“寻找失主的警察”,我们让“寻找失主的警察”来订阅小偷。

小偷被抓后,他把偷到的钱包作为数据传递给了警局,“寻找失主的警察”要在上交的钱包里找到失主的身份证,并把失主的身份证号码记录下来,然后调用“lookForOwner(iDnumber)”的方法,来找到失主,代码怎么体现呢?

//模拟钱包里装了一张身份证,身份证的号码是“123123123123123”
     Wallet wallet = new Wallet();
     IDcard iDcard = new IDcard();
     iDcard.iDnumber="123123123123123";
     wallet.iDcard=iDcard;
     //小偷把钱包作为数据传递给警察
      Observable.just(wallet).subscribe(new Action1<Wallet>() {
          @Override
          public void call(Wallet wallet) {
              //警察打开钱包,找到了身份证
              IDcard iDcard = wallet.iDcard;
              //警察记录下身份证的号码
              String iDnumber = iDcard.iDnumber;
              //警察通过身份证号码去找失主
              lookForOwner(iDnumber);
          }
      });

貌似需求已经解决了,但是“寻找失物的警察”这个观察者,做了太多的事情,他要翻开钱包找身份证,还得记录身份证号码,还得调用方法来找失主,警察好烦呀。碰巧这又是一个傲娇的警花,她会说:“人家不会翻钱包,钱包又脏又臭的,你直接告诉我失主的身份证号码,其他的事情不归我管!!!!!!!!!”。

所以说:这种方式仍然不能让人满意,因为我希望我的Subscribers(观察者:警察)越轻量越好,做的事情越少越好。另外,根据响应式函数编程的概念,Subscribers更应该做的事情是“响应”,响应Observable发出的事件,而不是去修改。如果我能在某些中间步骤中对“wallet”进行变换,使其转变成“IDnumber”,再把“IDnumber”传递给Subscribers(观察者:警察),那么警花做的事情就很少了,警花就会很开心。

使用操作符:map就是用来把一个事件转换为另一个事件的操作符,看看代码怎么实现吧

    //小偷把钱包作为数据传递给警察
     Observable.just(wallet)
     .map(new Func1<
      Wallet //接收到的数据类型,
      String //要转换成的数据类型
      >() {
            @Override
            public String call(Wallet wallet) {
                return wallet.iDcard.iDnumber;//在这里完成了翻钱包、找身份证、记录身份证号码的操作
            }
        }).subscribe(new Action1<
        String //接收到的数据类型
        >() {
            @Override
            public void call(String iDnumber /接收到map转换后的数据:一个身份证号码) {
                lookForOwner(iDnumber);//警花好开心,只需要拿着身份证号码去找失主就OK了
            }
        });

为了注释那几个泛型,使得代码阅读性降低,大家自行去掉注释,会好看一些,我觉得这几个泛型的意义是很重要的,它体现了“转换”这个精髓

这就是map的作用,把一件事情转换成了另一件事情,使得观察者只接受自己关心的数据(在读这段时,是不是脑子里还在想着那只被宰割的鸡)

这是接触的第一个操作符,所以说的比较详细,因为我刚开始也是,不能理解到为什么要转换呀,观察者拿到数据,自己操作不就行了吗?大家记住这个傲娇的警花,就能领会map转换的精髓了.

其实map操作符还有一个重要的使用场景:如果翻找钱包、记录身份证号码这些繁琐操作警花根据身份证号码找失主这个操作处于不同的线程时,你通过map操作符将这两种操作分离后,就可以通过Scheduler 来控制线程,关于线程控制(RxJava精华所在)稍后再说

9.听说flatMap是一个很有用但非常难理解的变换,说说flatMap吧

答:

这次咱们不说那个小偷了,这次警察抓住的是一个江洋大盗,被捕之后,他供出了所有的赃物,赃物在一个旅行包里,旅行包里装了100个钱包,钱包里有100个失主的身份证,傲娇的警花要通过100个身份证号码去找这100个失主。警花瞬间憔悴了,甚至想辞职不干了。

好吧,我们试着用map来解决这个事情

    //模拟一个包内,装了100个钱包,每个钱包里有一个身份证,身份证号码是"1001001"+i
        Bag bag = new Bag();
        List<Wallet> wallets=new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Wallet wallet = new Wallet();
            IDcard iDcard = new IDcard();
            iDcard.iDnumber="1001001"+i;
            wallet.iDcard=iDcard;
            wallets.add(wallet);
        }
        bag.wallets=wallets;

     Observable.just(bag).map(new Func1<Bag, List<Wallet>>() {
            @Override
            public List<Wallet> call(Bag bag) {
                return bag.wallets;
            }
            //在这里通过map,把旅行包bag,变换成了一堆钱包List<Wallet>,我们帮警花省去了打开旅行包这一步骤,我们还想把这一堆钱包List<Wallet>,变换成一个个钱包Wallet,并把找到每个钱包里的身份证,把身份证号码记录下来,直接把一个个身份证号码交给警花。最后的两件事map可以做到(上文已经提到了),但是我们卡在了将List<Wallet>变换成一个个钱包Wallet这一步,map无法做到这一点
        }).subscribe(new Action1<List<Wallet>>() {
            @Override
            public void call(List<Wallet> wallets) {
            //由于map做不到List<Wallet>变换Wallet,所以劳累警花自己遍历100个钱包
                for (Wallet wallet:wallets){
                    String iDnumber = wallet.iDcard.iDnumber;
                    lookForOwner(iDnumber);
                }
            }
        });

    private void lookForOwner(String iDnumber) {
        Log.e("警花","根据身份证号码"+iDnumber+"找到了失主");
    }

下面log打印

    07-19 23:27:32.405 2959-2959/pic.com.rxjavademo E/警花: 根据身份证号码10010010找到了失主
    07-19 23:27:32.405 2959-2959/pic.com.rxjavademo E/警花: 根据身份证号码10010011找到了失主
    07-19 23:27:32.405 2959-2959/pic.com.rxjavademo E/警花: 根据身份证号码10010012找到了失主
    ..............
    ..............
    ..............
    07-19 23:27:32.406 2959-2959/pic.com.rxjavademo E/警花: 根据身份证号码100100198找到了失主
    07-19 23:27:32.406 2959-2959/pic.com.rxjavademo E/警花: 根据身份证号码100100199找到了失主

代码注释里已经把问题说的很清楚了,我们该如何帮助警花呢?

map() 是一对一的转化,而我现在的要求是一对多的转化。

flatMap出场了:他专门负责一对多的转换

     Observable.just(bag).map(new Func1<Bag, List<Wallet>>() {//通过第一个map,从旅行包变换成一堆钱包
            @Override
            public List<Wallet> call(Bag bag) {
                return bag.wallets;
            }
        }).flatMap(new Func1<List<Wallet>, Observable<Wallet>>() {//通过flatMap,从一堆钱包变换成一个个钱包
            @Override
            public Observable<Wallet> call(List<Wallet> wallets) {
                return Observable.from(wallets);
            }
        }).map(new Func1<Wallet, IDcard>() {//通过第二个map,从一个个钱包,变换成了每个钱包里的身份证
            @Override
            public IDcard call(Wallet wallet) {
                return wallet.iDcard;
            }
        }).map(new Func1<IDcard, String>() {//通过第三个map,从每个钱包里的身份证,记录下对应的身份证号码,而这个恰恰是警花所关心的数据
            @Override
            public String call(IDcard iDcard) {
                return iDcard.iDnumber;
            }
        }).subscribe(new Action1<String>() {
            @Override
            public void call(String iDnumber) {//警花拿到了自己关心的数据身份证号码,执行了寻找失主的操作
                lookForOwner(iDnumber);
            }
        });

上面就是通过使用flatMap后,给观察者带来的便利,让观察者只拿到自己真正关心的数据。同时,也看到了map和flatMap是可以一致嵌套的,在嵌套的过程中,可以把每一步变换出来的数据交给需要的观察者(更重要的可以随意切换线程,稍后再说)。(不要觉得代码变长了,但是逻辑、流程变得很明晰)

从上面的代码可以看出, flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和

map() 不同的是, flatMap() 中返回的是个 Observable 对象,并且这个 Observable

对象并不是被直接发送到了 Subscriber 的回调方法中。 flatMap() 的原理是这样的:

1. 使用传入的事件对象创建一个 Observable 对象;

2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;

3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。

这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是

flatMap() 所谓的 flat。

未完待续

时间: 2024-10-18 17:50:11

RxJava入门第八、九问(二)的相关文章

Android入门第八篇之GridView(九宫图)

GridView跟ListView都是比较常用的多控件布局,而GridView更是实现九宫图的首选!本文就是介绍如何使用GridView实现九宫图.GridView的用法很多,网上介绍最多的方法就是自己实现一个ImageAdapter继承BaseAdapter,再供GridView使用,类似这种的方法本文不再重复,本文介绍的GridView用法跟前文ListView的极其类似....也算是我偷懒一下,嘻嘻嘻嘻.... 先来贴出本文代码运行的结果: 本文需要添加/修改3个文件:main.xml.n

Spring入门第十九课

后置通知 看代码: package logan.study.aop.impl; public interface ArithmeticCalculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); } package logan.study.aop.impl; import org.springframework.stereotype.Componen

CTF---Web入门第八题 Guess Next Session

Guess Next Session分值:10 来源: iFurySt 难度:易 参与人数:3870人 Get Flag:1672人 答题人数:1690人 解题通过率:99% 写个算法没准就算出来了,23333 hint:你确定你有认真看判断条件? 格式:CTF{} 解题链接: http://ctf5.shiyanbar.com/web/Session.php 原题链接:http://www.shiyanbar.com/ctf/1788 [解题报告] 这是我入门Web开始写的第八道题,打开解题链

Spring入门第八课

看如下代码 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframew

Delphi APP 開發入門(九)拍照與分享

Delphi APP 開發入門(九)拍照與分享 分享: Share on facebookShare on twitterShare on google_plusone_share 閲讀次數:3022 發表時間:2014/07/01 tags: 行動開發 教學 App Delphi XE6 Android iOS Delphi APP 開發入門(八)SQLite資料庫 << 前情 在眾多行動APP中,除了遊戲類之外,熱門的APP不外乎都會與拍照.分享.地圖等相關的技術有關聯,今天筆者就與朋友們

Android零基础入门第64节:揭开RecyclerView庐山真面目

大家还记得之前在第38期~第50期都在学习列表控件吗,其中用了8期讲ListView的使用,相信都已经掌握好了吧.那么本期一起来学习Android 5.X新增的一个列表组件,那就是RecyclerView的使用. 一.RecyclerView概述 从前面的学习我们知道,ListView的功能非常强大,几乎绝大部分应用程序都会使用到,虽然也学会一些方法技巧来提升ListView的效率,但其性能还是不是很完美. 另外ListView的可扩展性相对来说比较弱,以前要实现每个列表项的高度不同的界面,或者

Android零基础入门第61节:滚动视图ScrollView

原文:Android零基础入门第61节:滚动视图ScrollView 前面几期学习了ProgressBar系列组件.ViewAnimator系列组件.Picker系列组件和时间日期系列组件,接下来几期继续来学习常见的其他组件. 一.ScrollView概述 从前面的学习有的同学可能已经发现,当拥有很多内容时屏幕显示不完,显示不全的部分完全看不见.但是在实际项目里面,很多内容都不止一个屏幕宽度或高度,那怎么办呢?那就需要本节学习的ScrollView来完成. 在默认情况下,ScrollView只是

Android零基础入门第60节:日历视图CalendarView和定时器Chronometer

原文:Android零基础入门第60节:日历视图CalendarView和定时器Chronometer 上一期学习了AnalogClock.DigitalClock和TextClock时钟组件,本期继续来学习日历视图CalendarView和定时器Chronometer. 一.CalendarView 日历视图(CalendarView)可用于显示和选择日期,用户既可选择一个日期,也可通过触 摸来滚动日历.如果希望监控该组件的日期改变,则可调用CalendarView的 setOnDateCha

Android零基础入门第62节:搜索框组件SearchView

原文:Android零基础入门第62节:搜索框组件SearchView 一.SearchView概述 SearchView是搜索框组件,它可以让用户在文本框内输入文字,并允许通过监听器监控用户输入,当用户输入完成后提交搜索时,也可通过监听器执行实际的搜索. SearchView默认是展示一个search的icon,点击icon展开搜索框,也可以自己设定图标.用SearchView时可指定如下表所示的常见XML属性及相关方法. 如果为SearchView增加一个配套的ListView,则可以为Se