Kotlin + RxJava = Functional Powerhouse

原文链接 : Kotlin + RxJava = Functional Powerhouse
原文作者 : Thomas Nield
译文出自 : Hanks.xyz
译者 : hanks-zyh

作为一个软件开发人员,我总是寻找方法来达到更少的代码做更多的事情。如果我只要修改一点代码就可以不断适应业务需求变化那就更好了,所以我必须把代码重写。

Java语言一直是我的首选,因为它实用、可伸缩的、高性能、便携和静态类型。我在项目中熟练的运用Java,但我开始感到它比较麻烦(我一直关注着C#)。幸好去年我发现了RxJava,响应式编程使我完成的任务。

我几乎在我所有的项目总使用RxJava,我变得更高效,我的应用程序的质量也得到提高。但我慢慢开始意识到Java语言的局限性阻碍了RxJava。尽管Java 8提供了lambdas ,一些函数式编程任务还是非常冗长。

例如,使用 compose() 操作符接收一个Transformer,这样你可以使用现有的RxJava操作符来自定义操作符。但是问题是它很快就变得冗长了。

一个简单的例子。我可以创建一个 Transformer,将一个 Observable<T> 变成一个 <ImmutableList<T>> 因为我喜欢Google Guava’s 的不可变的集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final class Launcher {
public static void main(String[] args) {
Observable<String> source = Observable.just("Alpha",
"Beta", "Gamma", "Delta", "Epsilon");
source.compose(toImmutableList()).subscribe(System.out::println);
}
public static <T> Observable.Transformer<T,ImmutableList<T>> toImmutableList() {
return obs -> obs.collect(() -> ImmutableList.<T>builder(),
(b,t) -> b.add(t)).map(b -> b.build());
}
}

使用了Java 8能简化一大部分代码。但 toImmutableList这个函数和main函数在同一个类中,如果我放在一个单独的工厂类中,它将慢慢开始冗长了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class Launcher {
public static void main(String[] args) {
Observable<String> source = Observable.just("Alpha", "Beta", "Gamma",
"Delta", "Epsilon");
source.compose(GuavaTransformers.toImmutableList()).subscribe(System.out::println);
}
/*This would be in the GuavaTransformers class */
public static <T> Observable.Transformer<T,ImmutableList<T>> toImmutableList() {
return obs -> obs.collect(() -> ImmutableList.<T>builder(),
(b,t) -> b.add(t)).map(b -> b.build());
}
}

更糟糕的是,如果我开始创建更复杂的 Transformers 或带参数的操作符, compose() 语句开始变得很丑。如果我想给 ImmutableListMultimap 添加item,它开始变得更不友好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public final class JavaLauncher {
public static void main(String[] args) {
Observable<String> source = Observable.just("Alpha", "Beta", "Gamma",
"Delta", "Epsilon");
source.compose(GuavaTransformers
.toImmutableListMultiMap(s -> s.length())).subscribe(System.out::println);
}
/*This would be in the GuavaTransformers class */
public static <T> Observable.Transformer<T,ImmutableList<T>> toImmutableList() {
return obs -> obs.collect(() -> ImmutableList.<T>builder(),
(b,t) -> b.add(t)).map(b -> b.build());
}
public static <T,K> Observable.Transformer<T,ImmutableListMultimap<K,T>>
toImmutableListMultiMap(Func1<T,K> keyMapper) {
return obs -> obs.collect(() -> ImmutableListMultimap.<K,T>builder(),
(b,t) -> b.put(keyMapper.call(t), t)).map(b -> b.build());
}
}

这可能是个小例子,但是对于较大的应用这些问题可以迅速成为放大。即便使用了RxJava和Java 8的lambdas, 原来的代码也会慢慢变得难以理解,甚至我们还没涉及到元组和数据类的相关内容! 但 Kotlin 解决了所有的问题。

Introducing Kotlin

我试着看了Scala、Python和其他语言。我特别看了Scala,尽管它很厉害,但我发现它太深奥。然后有一天我发现JetBrains分享他们的新语言称为 Kotlin 。他们宣传它作为工业级,业务性的语言,强调实用性而不是便捷。JetBrains, 流行的Java IDE Intellij IDEA的创造者, 建造它,因为他们觉得他们可以更有效率的使用一种语言,Java。在学习Kotlin 和重写两个国内项目学习后,我很快就被安利并且准备使用它。Kotlin 可以和Java相互调用使它更好安利了。

但在这篇文章中,我想分享我使用KotlinRxJava的经验。具有讽刺意味的是,我发现RxJava和Kotlin一起用比和Java一起更好用本身。它只是表示函数式编程的概念。

例如, 我可以通过Observable的扩展方法“添加”一些方法,甚至不用扩展类!这并不是什么新鲜事,如果你学过C#, 但这对于Java来说是一等一的大事。下面我使用Kotlin来添加toImmutableList()toImmutableListMultimap()。然后我可以直接调用这些方法的而不用使用compose()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main(args: Array<String>) {
val source = Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
source.toImmutableListMultimap { it.length }.subscribe { println(it) }
}
fun <T> Observable<T>.toImmutableList() =
collect({ ImmutableList.builder<T>()},{ b, t -> b.add(t)}).map { it.build() }
inline fun <K,T> Observable<T>.toImmutableListMultimap(
crossinline keyMapper: (T) -> K) = collect({ ImmutableListMultimap.builder<K,T>()},
{ b, t -> b.put(keyMapper(t), t)}).map { it.build() }

这里有很多值得注意的地方:

  1. 我们没有将这些函数在类的内部。不像Java,Kotlin并不强迫你把静态方法在类。这很有用,有助于消除很多样板,特别是对于程序的程序。

  2. 变量类型可以通过类型推断,我们不必显示的声明这是一个Observable类型的变量。如果你想声明,可以使用下面的样式。在Kotlin类型是在变量名后面(:隔开)。之所以这样做, 是因为变量名很可能比类型更有用,所以它是宣先定义变量名。

1
2
val source: Observable<String> = Observable.just("Alpha", "Beta", "Gamma",
"Delta", "Epsilon")
  1. 你可以更容易的使用Lambdas。而不必写出一个像 s -> s.length()这样一对一的映射(),你可以用更简洁的表达他的长度。指的是单输入项排放(这是出现在其他JVM语言)。同样,没有paranthesis()接收参数。你使用{ }表达整个函数的运算符。这特别有用,你随时可以把多行放在一个{ }内。
1
2
3
4
5
6
7
8
9
source. toImmutableListMultimap { it. length }. subscribe{ println(it)}
```
4. 你可以使用扩展函数“添加”函数/方法到一个类中而不用创建一个新的子类。下面这条语句添加一个`toImmutableList()`函数,它可以在程序中被直接访问(除非你让它私有或改变其范围)。这是怎么做的?编译器只是在编译成字节码时使它成为了一个静态方法,但是你得到了好的语法糖以及看到它自动完成。你不需要目标泛型类型与扩展方法。例如,我可以做一个concatStr()扩展方法专门针对 `Observable<String>`而不是`Observable<T>`。
```kotlin
fun <T> Observable<T>.toImmutableList() =
collect({ ImmutableList.builder<T>()},{ b, t -> b.add(t)}).map { it.build()}
  1. 函数参数类型是简单得多。而不是表达一种功能性Func1 < T,K >,您可以使用一个SAM-less类型表达式(T)- > K .这个就更容易,这个函数接收一个T和把它变成一种K .它不是一个抽象方法(SAM)这使得它更容易的原因和遗漏的问题“我正在使用单一方法接口?”。当然,Kotlin将处理转换λ山姆当调用Java库,但是它不会用Kotlin。此外,使用内联函数接受函数参数和crossinline关键词,就可以获得巨大的效率通过消除对象的开销。
1
2
3
inline fun <K,T> Observable<T>.toImmutableListMultimap(
crossinline keyMapper: (T) -> K) = collect({ImmutableListMultimap.builder<K,T>()},
{ b, t -> b.put(keyMapper(t), t)}).map { it.build() }

Data Classes

Another great feature of Kotlin is data classes. Have you ever wanted to simply zip two values together, but had to create an entire class just to pair them up with hashCode(), equals(), and toString() implemented?
Kotlin的另一个特性是数据类。你有没有遇到过当你想简单地压缩两个值,但必须创建一个完整的类来并且实现hashCode(),equals()和toString()?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public final class JavaLauncher {
public static void main(String[] args) {
Observable<String> letter = Observable.just("Alpha", "Beta", "Gamma",
"Delta", "Epsilon");
Observable<Integer> number = Observable.just(1,2,3,4,5);
Observable<CodePair> zipped = Observable.zip(letter,number,
(l,n) -> new CodePair(l,n));
zipped.subscribe(System.out::println);
}
private static final class CodePair {
private final String letter;
private final Integer number;
CodePair(String letter, Integer number) {
this.letter = letter;
this.number = number;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CodePair codePair = (CodePair) o;
if (!letter.equals(codePair.letter)) return false;
return number.equals(codePair.number);
}
@Override
public int hashCode() {
int result = letter.hashCode();
result = 31 * result + number.hashCode();
return result;
}
@Override
public String toString() {
return "CodePair{" +
"letter='" + letter + '\'' +
", number=" + number +
'}';
}
}
}

这很不爽,我不得不写36行代码来创建一个带有两个属性的CodePair类。这个问题多次出现在函数式编程中,唯一的选择是创建元组,这只会使代码更加难懂。

但是在Kotlin,你可以声明一个data class。这允许您快速声明一个类,在一行声明所有的属性,它会照顾hashCode(), equals(), toString()和实现builder。

48行混乱的Java代码在Kotlin现在变成5行。

1
2
3
4
5
6
7
8
9
10
11
fun main(args: Array<String>) {
data class CodePair(val letter: String, val number: Int)
val letter = Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
val number = Observable.just(1, 2, 3, 4, 5)
val zipped = Observable.zip(letter, number) { l, n -> CodePair(l, n) }
zipped.subscribe { System.out.println(it) }
}

我们在main函数内声明CodePair类,它只存在于main函数的范围内。它有命名属性字母和数字你可以访问。这在Java看起来不切实际。能够动态声明简单类,常见的方法实现做了,使得开发者可以快速开发出组织清晰的代码。

Conclusions

我只简单分享Kotlin能做什么,有或没有RxJava。这不是一个教程,只是快速展示如何在KotlinRxJava表达不同。我希望通过分享我的经验使你想去了解一下Kotlin。我知道与Scala结合RxScala可以做很多事情,但Kotlin是不同的。它既像Java一样强大又像Python一样灵活。当你把RxJava扔到混合Kotlin,我发现这是一个非常好的结合。我忘记提到没有原始类型和装箱拆箱? Kotlin有很多特性,将范围发布在这里,像空指针安全

Kotlin也支持Android。你也可以checkoutRxKotlin库(RxJava的扩展,利用了Kotlin功能(如给集合添加toObservable()方法)。

文章来自: https://hanks.pub