在编程语言里面,控制结构是比较基础的一块内容。常见的是顺序,分支和循环。部分语言有try catch finally结构等。在使用RxJava时,如何对应既有的控制结构,或者说既有代码如何转换成RxJava的代码,是学习RxJava时必须熟练掌握的东西。
顺序结构
在三种常见控制结构里面,顺序其实是比较难的一种结构。比如说以下两行顺序代码
a = operation1() b = operation2(a)
和另外一类顺序代码
a = operation1() b = operation2()
operation2是否需要上一行操作的结果对如何转换成RxJava的代码有很大的影响。
对于第一种有依赖关系的代码来说,需要使用flatMap
Single<A> operation1() {...} Single<B> operation2(A a) {...} operation1 .flatMap(a -> operation2(a))
因为有依赖关系,operation2不能与operation1同时进行。
相反,如何operation2不依赖operation1的结果的话,理论上可以同时执行。
比如说上面第二类顺序代码
Single<A> operation1() {...} Single<B> operation2() {...} Single.zip(operation1(), operation2(), (a, b) -> {...})
这里通过zip让两个操作同时进行。这是一般顺序代码难以表达的结构。
不过,仍旧存在一类代码,虽然两个操作没有直接依赖关系,但是隐藏着第一个操作失败之后,第二个操作不应该执行的错误依赖要求。对于这类代码,就不应该使用zip让两个操作并行,你需要退回flatMap的方式。
分支结构
分支结构相对简单,比如说最常见的单个if
if(condition) { // do something }
if加else
if(condition) { // do something } else { // do something }
if加else if
if(condition) { // do something } else if(condition2) { // do something } else { // do something }
当然还有switch表达式,由于switch可以用if来替换,这里不再展开
不管是哪种if,在RxJava里都必须转换为一个有返回值的表达式才能使用。比如说
int b = 0; if(a == 1) { b = 1; }
用RxJava的话
Single<Integer> sourceA = Single.just(1); sourceA.flatMap(a -> { if(a == 1) { return Single.just(1); } return Single.just(0); })
可以看到这里使用了flatMap,并且if判断被放到了flatMap内部。
和顺序结构时用的flatMap不同,顺序结构时的flatMap主要是考虑到数据依赖,以及异常情况的fail fast,分支结构时用的if,既可以抛出异常,也可以返回不同的值。
抛出异常的例子
int operation(int a) { if(a < 0) { throw new IllegalArgumentException("a < 0"); } // do stuff }
改写为RxJava风格
Single<Integer> sourceA = ... sourceA.flatMap(a -> { if(a < 0) { return Single.error(new IllegalArgumentException("a < 0")); } // other case })
这里由于a来自一个Single流,使用flatMap串了起来。如果a只是一个普通方法参数,直接写if判断可能更好,不是所有时候都需要改成异步。
返回不同的值,也比较简单,看情况你可以使用map而不是flatMap。比如说开始的例子
Single<Integer> sourceA = Single.just(1); sourceA.flatMap(a -> { if(a == 1) { return Single.just(1); } return Single.just(0); });
可以改成
Single<Integer> sourceA = Single.just(1); sourceA.map(a -> { if(a == 1) { return 1; } return 0; });
甚至更短
Single<Integer> sourceA = Single.just(1); sourceA.map(a -> a == 1 ? 1 : 0);
如果没有异常,或者一次肯定返回一个值的话,用map,并且在map里加上判断逻辑即可。
需要注意的是,在写同步代码的时候,有一种风格是优先把异常情况排除掉,在转换为RxJava风格时你可能需要灵活处理,包括使用异常,使用中间结果等等。比如对下面的代码
void operation(int a, Context ctx) { if(a < 0) { ctx.statusCode(403); return; } operation2(a); ctx.statusCode(200); ctx.write("done"); }
在转换的时候,你你可以选择设计一个特殊的异常用于区分,也可以把statusCode直接作为结果
class OperationException extends RuntimeException { private final int errorCode; OperationException(int errorCode) { super(); this.errorCode = errorCode; } int getErrorCode() { return this.errorCode; } } Single<Integer> sourceA = ...; sourceA .flatMap(a -> { if(a < 0) { return Single.error(new OperationException(403)); // or Single.just(403) } return operation2(a).andThen(Single.just(200)); });
循环结构
接下来一个控制结构是循环。一般的循环转换为RxJava都很简单。比如说
for(int n : ns) { int m = operation(n); // do something with m } Observable.fromIterable(ns) .map(n -> operation(n)) .subscribe(ms -> ...)
如果有条件,可以通过filter/takeWhile等
循环的目的是聚合,比如说计算总和的话,可以用reduce
因为函数式语言本身在这块支持比较丰富,表达性也比同步代码要清晰
不过如果你的代码中有break/continue之类的话,特别是比较复杂的break/continue时,最坏情况下你需要退回flatMap,在flatMap中处理复杂逻辑。
trycatch结构
最后讲一下如何转换try catch finally。这个结构中比较难的是finally,比如典型的IO处理
IOResource ior = ... try { // do with ior } catch(IOException e) { // do with exception } finally { ior.close(); }
转换成RxJava风格时,你需要注意正常情况和异常情况下都需要调用close
Single<IOResource> sourceIor = ... sourceIor.flatMap(ior -> ior.operationWithResult() .onErrorResumeNext(e -> ior.close().andThen(Single.error(e)) .flatMap(r -> ior.close().andThen(Single.just(r)) ); sourceIor.flatMap(ior -> ior.operationWithoutResult() .onErrorResumeNext(e -> ior.close().andThen(Completable.error(e)) .andThen(() -> ior.close()) );
这里使用了onErrorResumeNext。onErrorResumeNext其实放在哪个位置其实不是很重要,但是放在最后可以组成一个可以复用的operator。
总的来说,学会如何把既有结构转换成RxJava风格的代码之后,可以说学会了RxJava的一半。希望上述内容对你有用。