天天看点

[译] 大战 RxJava2 和 Java8 Stream [ Android RxJava2 ] (这到底是什么) 第四部分

<b>本文讲的是[译] 大战 RxJava2 和 Java8 Stream [ Android RxJava2 ] (这到底是什么) 第四部分,</b>

又是新的一天,如果学点新东西,这一天一定会很酷炫。

动机:

现在我们已经学会了一些预备知识,但当时我什么都不懂。因此我开始学习如何根据数据或对象创建 Observable 。然后知道了当 Observable 的数据发生变化时,应该调用哪些接口(或者可以叫做“回调”)。这在理论上很好,但是当我付诸实践的时候,却 GG 了。我发现很多理论上应该成立的模式在我去用的时候完全不起作用。对我来说最大的问题,是不能用响应或者函数式响应的思维思考问题。我熟悉命令式编程和面向对象编程,由于先入为主,所以对我来说理解响应式会有些难。我一直在问这些问题:我该在哪里实现?我应该怎么实现?如果你能坚持看完这篇文章,我可以 100% 保证你会知道怎样把命令式代码转换成 Rx 代码,虽然写出来的 Rx 代码不是最好的,但至少你知道该从哪里入手了。

回顾:

函数式接口是只有一个抽象方法的接口。

在 Java8 我们可以在接口中定义方法,这种方法叫做“默认方法”。

至少有一个参数是函数的函数和返回类型为函数的函数称为高阶函数。

纯函数的返回值仅仅由参数决定,不会产生可见的副作用(比如修改一些影响程序状态的值。——译注)。

Lambda 表达式在计算机编程中又叫做匿名函数,是一种在声明和执行的时候不会跟标识符绑定的函数或者子程序。

简介:

今天我们将向 RxJava 的学习宣战。我确定在最后我们会取得胜利。

作战策略:

Java8 Stream(这使得我们快速开始,我们将从 Android 开发者的角度来看)

Java8 Stream 向 Rx Observable 转变

RxJava2 Android 示例

技巧,怎样把命令式代码转为 RxJava2 Android 代码

是时候根据我们的策略发动进攻了,兄弟们上。

1. Java8 Stream:

现在我用 IntelliJ 这个 IDE 来写 Java8 的 Stream。你可能会想为什么我去使用在 Android 不支持的 Java8 的 Stream。对于这样想的同志,我来解释一下。主要有两个原因。首先,我知道几年后 Java8 将成为 Android 开发的一等公民。所以你应该了解关于 Stream 的 API,并且在面试中你可能被问到。而且,Java8 的 Stream 和 Rx Observable 在概念上很像。所以,为什么不一次性把这两个东西一起学了呢?其次,我感觉很多像我一样能力低下、懒惰并且不容易掌握概念的同志也可以在几分钟内了解这个概念。再次强调,我向你们 100% 地保证。通过学习 Java8 的 Stream 可以让你很快地学会 Rx。好,我们开始了。

Stream:

支持在元素形成的流上进行函数式操作(比如在集合上进行的 map-reduce 变换)的类(docs.oracle)。

第一个问题:在英语中 Stream 是什么意思?

答案:一条很窄的小河,或者源源不断流动的液体、空气、气体。在编程的时候把数据转化成“流”的形式,比如我有一个字符串,但是我想把它变成“流”来使用的话我需要干些什么,我需要创建一个机制,使这个字符串满足“源源不断流动的液体、空气、气体 {或者数据}”的定义。问题是,我们为什么想要自己的数据变成“流”呢,下面是个简单的例子。

就像下面这幅图中画的那样,我有一杯混合着大大小小石子的蓝色的水。

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_1.jpg" target="_blank"></a>

现在按照我们关于“流”的定义,我用下图中的方法将水转化成“流”。

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_2.jpg" target="_blank"></a>

为了让水变成水流,我把水从一个杯子倒进另一个杯子 里。现在我想去掉水中的大石子,所以我造了一个可以帮我滤掉大石子的过滤器。“大石子过滤器”如下图所示。

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_3.jpg" target="_blank"></a>

现在,将这个过滤器作用在水流上,这会得到不包含大石子的水。如下图所示。

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_4.jpeg" target="_blank"></a>

哈哈哈。 接下来,我想从水中清除掉所有石子。已经有一个过滤大石子的过滤器了,我们需要造一个新的来过滤小石子。“小石子过滤器”如下图所示。

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_5.jpg" target="_blank"></a>

像下图这样,将两个过滤器同时作用于水流上。

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_6.png" target="_blank"></a>

哇哦~ 我已经感觉到你们领悟了我说的在编程中使用流所带来的好处是什么了。接下来,我想把水的颜色从蓝色变成黑色。为了达到这个目的,我需要造一个像下图这样的“水颜色转换器(mapper)”。

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_7.jpg" target="_blank"></a>

像下图这样使用这个转换器。

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_8.jpg" target="_blank"></a>

把水转换成水流后,我们做了很多事情。我先用一个过滤器去掉了大石子,然后用另一个过滤器去掉了小石子, 最后用一个转换器(map)把水的颜色从蓝色变成黑色。

当我将数据转换成流时,我将在编程中得到同样的好处。现在,我将把这个例子转换成代码。我要显示的代码是真正的代码。可能示例代码不能工作,但我将要使用的操作符和 API 是真实的,我们将在后面的实例中使用。所以,同志们不要把关注点放在编译上。通过这个例子,我有一种感觉,我们将很容易地把握这些概念。在这个例子中,重要的一点是,我使用 Java8 的 Stream API 而不是 Rx API。我不想让事情变困难,但稍后我也会使用 Rx。

图像中的水 &amp; 代码中的水:

输出:

water

big stone

small stone

图像中的水流 &amp; 代码中的水流:

图像中的“大石子过滤器” &amp; 代码中的“大石子过滤器”:

同志们这里需要注意下!

在 Java8 Stream 中有个叫做 Predicate(谓词,可以判断真假,详情见离散数学中的相关定义——译注)的函数式接口。所以,如果我想进行过滤的话,可以用这个函数式接口实现流的过滤功能。现在,我给大家展示在我们的代码中如何创建“大石子过滤器”。

图像和代码中的作用在水流上的“大石子过滤器”:

这里我使用了 forEach 方法,暂时把这当作流上的 for 循环。用在这里仅仅是为了输出。除去没有这个方法,我们也已经实现了我们在图像中表示的内容。是时候看看输出了:

没有大石子了,这意味着我们成功过滤了水。

图像中的“小石子过滤器” &amp; 代码中的“小石子过滤器”:

在图像和代码中使用“小石子过滤器”:

我不打算解释 SmallStoneFilter,它的实现和 BigStoneFilter 是一样一样的。这里我只展示输出。

图像中的“水颜色转换器” 和 代码中的“水颜色转换器”

同志们这里需要注意!

在 Java8 Stream 中有个叫做 Function 的函数式接口。所以,当我想进行转换的时候,需要把这个函数式接口送到流的转换(map)函数里面。现在,我给大家展示在我们的代码中如何创建“水颜色转换器”。

这是一个函数式接口,所以我可以把它转换为 Lambda :

简单来说,泛型中的第一个 String 代表我从水中得到什么,第二个 String 表示我会返回什么。 为了更好地掰扯清楚,我写了个把 Integer 转化成 String 的转换器。

回到我们原来的例子。

为水流添加颜色转换器的图像和代码:

water black

完活!现在我们再次回顾一些内容。

filter(过滤器): Stream 有一个只接受 Predicate 这个函数式接口的方法。我们可以在 Predicate 里写作用在数据上的逻辑代码。

map(映射):Stream 有一个只接受 Function 这个函数式接口的方法。我们可以在 Function 里写按照我们的要求转换数据的逻辑代码。

在进入下个环节之前,我想解释一个曾经困惑我很久的东西。当我们在任意数据上使用 stream() 的时候,背后是怎样工作的。所以我要举一个例子。我有一个整数列表。我想在控制台上显示它们。

使用命令式编程来打印数据:

使用 Stream 或 Rx 的方式来打印数据:

对于以上两段代码,它们的不同点在哪呢?

简单来说,在第一段代码中我自己管理 for 循环:

但是在第二段代码中,流(或者稍后后要展示的 Rx 中的 Observable)进行循环:

我认为很多事情都说清楚了,是时候用 Rx 来写个真实的例子了。在这个例子中,我会同时使用流式编码(stream code)和响应式编码(Rx code),这样大家可以更容易地掌握这俩的概念。

2. Java8 Stream to Rx Observable:

有一个存有 “Hello World” 的列表。 在图片中,把它视作字符串。在代码中把它看作列表,这样比较好解释。

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_9.jpg" target="_blank"></a>

Java8 的 Stream 代码:

Android 中的代码:

在这里展示了 Java8 代码和 Android 代码。从现在开始,我只给出代码中的响应式(Reactive)部分而不给出完整的一个类。完整代码分享在文章的最后了。上面的代码将变成这样:

Again above example:

这两者会有相同的结果,这样来输出整个列表:

是时候来比较下这俩了。

在 Java8 中我想要一个东西变成流的形式,我会用 Stream 的 API,但是在 Android 里,我先把那个东西转换成 Observable 然后获取到数据流。

接下来,我们将用 ’l‘ 作为过滤器来处理 Hello World,就像下面这样:

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_10.jpg" target="_blank"></a>

好。是时候对 Java8 的 Stream API 说再见了。

3. RxJava2 的 Android 示例:

有一个整数数组,我想让数组中的每个成员变成自身的平方。

如图所示:

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_12.png" target="_blank"></a>

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_13.jpeg" target="_blank"></a>

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_14.jpg" target="_blank"></a>

Android 代码:

03-12 16:13:32.432 14918-14918/async.waleed.rx I/Android: 1

03-12 16:13:32.432 14918-14918/async.waleed.rx I/Android: 4

03-12 16:13:32.432 14918-14918/async.waleed.rx I/Android: 9

03-12 16:13:32.432 14918-14918/async.waleed.rx I/Android: 16

这波很稳,我们之前已经用到过相同的概念了。把一个函数式接口传进 map,这个函数简单地将输入的数平方后返回。

稍有常识的人都知道,我们只能在 log 中打印字符串。在上面的代码中,我在整数值的后面添加 <code>+""</code> 来把他们转换成字符串。

哇哦!我们可以在这个例子中再用一次 map。你们都知道我需要把整数转换成字符串以便打印到 Logcat,但是我现在打算为 map 再写一个函数式接口来完成转换。这意味着我们不需要在数据后面添加 <code>+""</code>了,如下所示:

4. 如何把命令式代码转化成 RxJava2 Android 代码:

这里我打算使用一段现实存在于某 APP 的代码,我将使用 Rx Observable 把它转化成响应式(Reactive)代码。这样你很容易就知道怎样开始在自己的项目中使用 Rx 了。重要的东西可能不是很容易理解,但你应该开始动手,这样才会感觉良好。所以,像我在示例代码中提到的那样去使用它们,我会在下一篇文章中详细解释。尝试多去练练手。

示例:

<a href="https://link.juejin.im/?target=http%3A%2F%2Fwww.uwanttolearn.com%2Fwp-content%2Fuploads%2F2017%2F03%2Fwar_against_learning_curve_of_rx_java_2_java_8_stream_15.jpg" target="_blank"></a>

如果你观察得很仔细的话,可以看到我需要将选定的界面对应的点设置成黑色。

命令式编程的代码:

响应式代码(Rx)的代码:

在 setDots 函数中,我简单地遍历每个 ImageView 并且把它们设置成白色的空心圈,之后将选定的 ImageView 重新设定为实心圈。

或者,

在这个 setDots 函数中,我把除选定的 ImageView 之外的所有 ImageView 设置为白色空心圈。

之后,将选中的 ImageView 设置为实心圈。

4. 几个关于把命令式代码转换成响应式代码的技巧:

为了让大家可以在现有的代码上轻松开始使用 Rx,我写了几个小技巧。

如果代码中有循环的话,用 Observable 替换

如果代码中有 if 语句的话,用 Rx 中的 filter 替换

如果需要把一些数据转换为另一种格式,可以用 map 实现

在 Rx 中,有很多方法实现上述代码。

使用两个流:

在 map 中使用 if else :

如果代码中有嵌套的循环:

这里用到了 flatmap 这个新的操作符。先仅仅尝试像示例代码中那样使用,我会在下篇文章中解释。

总结:

<b></b>

<b>原文发布时间为:2017年4月17日</b>

<b>本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。</b>

继续阅读