嘿,小伙伴们,今天,我继续死磕自己。
写在前面
昨天发了第一篇将RxJava入门篇,由于小伙伴们上一篇反响还不错,让我很受鼓舞。公众号后台有小伙伴给我留言说,希望能在以后的文章里面写一个我自己总结的RxJava实际运用的Demo。我当然答应他了。给自己压力,就是自己在技术栈上前进的动力!不过不是今天这篇文章,预计在本周末会在我的GitHub上面开源一个关于RxJava,上手使用Demo,欢迎持续关注。
子曰:工欲善其事,必先利其器。小伙伴,可以一起带着这样的心境,一起走过这几篇文章的更新吧,先基础打牢,等到我们去实践这门技术的时候,就更加得心应手啦!大家一一起加油,一起升级打怪。
上一篇文章,我介绍了RxJava的一些基础知识,同时也介绍了map()操作符。当然如果你并没有意愿去使用RxJava我一点也不诧异。毕竟才接触了这么一点。看完这篇文章,我相信你肯定想立即在你的项目中使用RxJava了,这篇文章,将介绍许多RxJava的操作符,RxJava的强大性就来自于它所定义的操作符。
万年惯例,先来看一个例子:
准备工作
假设我有这样一个方法:
这个方法根据输入的字符串返回一个网站的url列表
Observable> query(String text);
现在我希望构建一个健壮系统,它可以查询字符串并显示结果。根据上一篇文章的内容,我们可能会写出下面的代码:
query("Hello,world!")
.subscribe(urls ->{
for(String url : urls){
System.out.println(url);
}
});
这种代码当然是不能容忍的,因为上面的代码使我们丧失了变化数据流的能力。一旦我们想要更改每一个URL,只能在Subscriber中来做。我们竟然没有使用如此之酷的map()操作符!
当然,我可以使用map操作符,map中输入的是urls的列表,处理的时候,还是要for each遍历,一样很蛋疼。
万幸,还有Observable.from()方法,它接收一个集合作为输入,然后每次输入一个元素给Subscriber:
Observabke.form("url1","url2","url3")
.subscribe(url -> System.out.println(url));
我们来把这个方法使用到刚才的场景:
query("Hello,world!")
.subscribe(urls ->{
Observable.from(urls)
.subscribe(url -> System.out.println(url));
});
看到这里,也许你会说,虽然去掉了for each循环,但是代码依然看起来很乱。多个嵌套的subscription不仅看起来很丑,代码难以维护,更严重的是它会破坏某些我们现在还没讲到的RxJava的特性。
改进
救世主来临的时刻,程序猿界总是充满着善意的爱,它就是flatMap().
Observable.flatMap()接受一个Observable的输出作为输入。同时输出另外一个Observable。直接看代码:
query("Hello,world!")
.flatMap(new Fun1, Observable>(){
@Override
public Observable call(List urls){
retuen Observable.from(urls);
}
})
.subscribe(url -> System.out.println(url));
这里我贴出了整个函数代码,以方便你了解发生了什么,使用Lambda可以大大简化代码长度:
query("Hello,world!")
.flatMap(urls -> Observable.from(urls))
.subscribe(url -> System.out.println(urs));
flatMap是不是看起来很奇怪?为什么它要返回另外一个Observable呢?理解flatMap的关键点在于,flatMap输出的新的Observable正是我们在Subscriber想要接收的。现在Subscriber不再收到List,而是收到一些单列的字符串,就像Observable.from()的输出一样。
这部分也就是我当初学RxJava的时候最难理解的部分,一旦领悟了,RxJava很对疑问也就有一并解决了。
还可以更好
flatMap()实在不能更赞了,它可以返回任何它想返回的Observable对象。
比如下面的方法:
1、返回网站的标题,如果404了就返回null
2、Observable getTitle(String url);
接着前面的例子,现在我不想打印URL了,而是要打印收到的每个网站的标题。问题来了,我的方法每次只能传入一个URL,并且返回的不是一个String,而是一个输出String的Observable对象。使用flatMap()可以简单的解决这个问题。
query("Hello,world!")
.flatMap(urls - >Observable.from(urls))
.flatMap(new Func1>(){
@Override
public Observable call(String url){
return getTitle(url);
}
})
.subscribe(title -> System.out.println(title));
使用Lambda:
query("Hello, world!")
.flatMap(urls - > Observable.from(urls))
.flatMap(url - > getTitle(url))
.subscribe(title - > System.out.println(title));
是不是感觉很不可思议?我竟然能将多个独立的返回Observable对象的方法组合在一起!帅呆了!不止这些,我还将两个API的调用组合到一个链式调用中了。我们可以将任意多个API调用链接起来。大家应该都知道同步所有API调用,然后将所有API调用的回调结果组合成需要展示的数据,这是一件多么蛋疼的事情。在这里,我们成功的避免了callback (多层嵌套的回调,导致代码难以阅读维护)。现在所有的逻辑都包装成了这种简单的响应式调用。
丰富的操作符
目前为止,我们已经接触了两个操作符,RxJava中还有更多的操作符,那么,我们如何使用其他操作符来改进我们的代码呢?
getTitle()返回null如果url 不存在。我们不想输出 “null” ,那么我们可以从返回的title列表中过滤掉null 值!
query("Hello,world!")
.flatMap(urls - > Observable.from(urls))
.flatMap(ur; - > getTitle(url))
.filter(title - > title !=null)
.subscribe(title - > System.out.println(title));
filter()输出和输入相同的元素,并且会过滤掉那些不满足检查条件的。
如果我们只想要最多5个结果:
query("Hello,world!")
.flatMap(urls - > Observable.from(urls))
.flatMap(url - > getTitle(url))
.filter(title - > title! =null)
.take(5)
.subscribe(title - > System.out.println(title));
take()输出最多指定数量的结果。
如果我们在打印之前,把每个标题保存到磁盘:
query("Hello,world!")
.flatMap(urls - > Observable.form(urls))
.flatMap(url - > getTitle(url))
.flatMap(title - > title !=null)
.take(5)
.doOnNext(title - > saveTitle(title))
.subscribe(title - > System.out.println(title));
doOnNext()允许我们在每次输出一个元素之前做一些额外的事情,比如这里的保存标题。小伙伴们,看到这里操作数据是多么简单了吗?你可以添加任意多的操作,并且不会搞乱你的代码。
RxJava包含了大量的操作符。操作符的数量是有点吓人,但是很值得你挨个去看一下,这样你可以知道哪些操作符可以使用。弄懂这些操作符可能会花一些时间,但是一旦弄懂了,你就完全掌握了RxJava的威力了。
总结
好吧,如果你是一个怀疑主义者,并且很难被说服,那么为什么要关心这些操作符呢?
因为操作符可以对数据流做任何操作。
将一系列的操作符链接起来就可以完成复杂的逻辑。代码被分解成一系列可以组合的片段。这就是响应式函数编程的魅力所在。用的越多,将会越多的改变你的编程思维。
另外,RxJava也使我们处理数据的方式变得简单。再最后一个例子里,我们调用了两个API,对API返回的数据进行了处理,然后保存到磁盘。但是我们的Subscriber并不知道这些,它只是认为自己在接收一个Observable对象。良好的封装性也带来了编码的便利!
嘿,小伙伴们,看到这里是不是觉得对RxJava相见恨晚呢?我现在就有这种感觉!
在我的公众号的下一篇文章里,我会继续介绍RxJava的另外一些很酷的特性,比如错误的处理和并发,不过,这些特性不会直接用来处理数据。
更多内容请关注我的个人微信公众号:前端开发技术栈