开始第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。
未完待续