在编程语言里面,控制结构是比较基础的一块内容。常见的是顺序,分支和循环。部分语言有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的一半。希望上述内容对你有用。