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

大战 RxJava2 和 Java8 Stream [ Android RxJava2 ] (这究竟是什么) 第四部分

又是新的一天,若是学点新东西,这一天必定会很酷炫。javascript

小伙伴们一切顺利啊,这是咱们的 RxJava2 Android 系列的第四部分 [ 第一部分第二部分第三部分 ]。 好消息是咱们已经作好准备,能够开始使用 Rx 了。在使用 RxJava2 Android Observable 以前,我会先用 Java8 的 Stream 来作响应式编程。我认为咱们应该了解 Java8,并且经过使用 Java8 的 Stream API 让我感受学习 RxJava2 Android 的过程更简单。
动机:前端

动机跟我在 第一部分 和你们分享过的同样。在我开始学习 RxJava2 Android 的时候,我并不知道本身会在什么地方,以何种方式使用到它。java

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

回顾:android

我想回顾以前三篇文章中咱们提到过的全部概念 [ 第一部分第二部分第三部分 ]。由于如今咱们要用到这些概念了。在 第一部分 咱们学习了观察者模式; 在 第二部分 学习了拉模式和推模式、命令式和响应式;在 第三部分 咱们学习了函数式接口(Functional Interfaces)、 接口默认方法(Default Methods)、高阶函数(Higher Order Functions)、函数的反作用(Side Effects in Functions)、纯函数(Pure Functions)、Lambda 表达式和函数式编程。我在下面写了一些定义(很无聊的东西)。若是你清楚这些定义,能够跳到下一部分。
函数式接口是只有一个抽象方法的接口。
在 Java8 咱们能够在接口中定义方法,这种方法叫作“默认方法”。
至少有一个参数是函数的函数和返回类型为函数的函数称为高阶函数。
纯函数的返回值仅仅由参数决定,不会产生可见的反作用(好比修改一些影响程序状态的值。——译注)。
Lambda 表达式在计算机编程中又叫作匿名函数,是一种在声明和执行的时候不会跟标识符绑定的函数或者子程序。ios

简介:git

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

做战策略:面试

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

  2. Java8 Stream 向 Rx Observable 转变

  3. RxJava2 Android 示例

  4. 技巧,怎样把命令式代码转为 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 是什么意思?

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

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

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

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

如今,将这个过滤器做用在水流上,这会获得不包含大石子的水。以下图所示。

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

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

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

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

把水转换成水流后,咱们作了不少事情。我先用一个过滤器去掉了大石子,而后用另外一个过滤器去掉了小石子, 最后用一个转换器(map)把水的颜色从蓝色变成黑色。

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

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

public static void main(String [] args){
    Water water = new Water("water",10, "big stone", 1 , "small stone", 3);
    // 含有一个大石子和三个小石子的十升水
    for (String s : water) {
        System.out.println(s);
    }
}复制代码

输出:
water
water
big stone
water
water
small stone
water
small stone
small stone
water
water
water
water
water

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

public static void main(String[] args) {
    Water water = new Water("water", 10, "big stone", 1, "small stone", 3);
    // 10 litre water with 1 big and 3 small stones.
    water.stream();
}

//输出和上面那个同样复制代码

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

同志们这里须要注意下!

在 Java8 Stream 中有个叫作 Predicate(谓词,能够判断真假,详情见离散数学中的相关定义——译注)的函数式接口。因此,若是我想进行过滤的话,能够用这个函数式接口实现流的过滤功能。如今,我给你们展现在咱们的代码中如何建立“大石子过滤器”。

private static Predicate<String> BigStoneFilter  = new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return !s.equals("big stone");
    }
};复制代码

正如咱们在 第三部分 所学到的,任何函数式接口均可以转换成 Lambda 表达式。把上面的代码转换成 Lambda 表达式:

private static Predicate<String> BigStoneFilter  = s -> !s.equals("big stone");复制代码

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

public static void main(String[] args) {
    Water water = new Water("water", 10, "big stone", 1, "small stone", 3);
    water.stream().filter(BigStoneFilter)
    .forEach(s-> System.out.println(s));

}

private static Predicate<String> BigStoneFilter  = s -> !s.equals("big stone");复制代码

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

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

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

private static Predicate<String> SmallStoneFilter  = s -> !s.equals("small stone");复制代码

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

public static void main(String[] args) {
    Water water = new Water("water", 10, "big stone", 1, "small stone", 3);
    water.stream()
            .filter(BigStoneFilter)
            .filter(SmallStoneFilter)
    .forEach(s-> System.out.println(s));
}

private static Predicate<String> BigStoneFilter  = s -> !s.equals("big stone");
private static Predicate<String> SmallStoneFilter  = s -> !s.equals("small stone");复制代码

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

water
water
water
water
water
water
water
water
water
water

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

同志们这里须要注意!

在 Java8 Stream 中有个叫作 Function 的函数式接口。因此,当我想进行转换的时候,须要把这个函数式接口送到流的转换(map)函数里面。如今,我给你们展现在咱们的代码中如何建立“水颜色转换器”。

private static Function<String, String > convertWaterColour = new Function<String, String>() {
    @Override
    public String apply(String s) {
        return s+" black";
    }
};复制代码

这是一个函数式接口,因此我能够把它转换为 Lambda :

private static Function<String, String > convertWaterColour = s -> s+" black";复制代码

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

private static Function<Integer, String > convertIntegerIntoString = i -> i+" ";复制代码

回到咱们原来的例子。

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

public static void main(String[] args) {
    Water water = new Water("water", 10, "big stone", 1, "small stone", 3);
    water.stream()
            .filter(BigStoneFilter)
            .filter(SmallStoneFilter)
            .map(convertWaterColour)
            .forEach(s -> System.out.println(s));
}

private static Predicate<String> BigStoneFilter = s -> !s.equals("big stone");
private static Predicate<String> SmallStoneFilter = s -> !s.equals("small stone");
private static Function<String, String> convertWaterColour = s -> s + " black";复制代码

输出:
water black
water black
water black
water black
water black
water black
water black
water black
water black
water black

完活!如今咱们再次回顾一些内容。

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

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

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

public static void main(String [] args){
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
}复制代码

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

public static void main(String [] args){
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);

    for (Integer integer : list) {        
        System.out.println(integer);   

     }
}复制代码

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

public static void main(String [] args){
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);

    list.stream().forEach(integer -> System.out.println(integer));

}复制代码

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

简单来讲,在第一段代码中我本身管理 for 循环:

for (Integer integer : list) {
        System.out.println(integer);
}复制代码

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

list.stream().forEach(integer -> System.out.println(integer));复制代码

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

2. Java8 Stream to Rx Observable:

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

Java8 的 Stream 代码:

public static void main(String [] args){

    List<String> list = new ArrayList<>();
    list.add("H");
    list.add("e");
    list.add("l");
    list.add("l");
    list.add("o");
    list.add(" ");
    list.add("W");
    list.add("o");
    list.add("r");
    list.add("l");
    list.add("d");
    list.stream(); // Java8
}复制代码

Android 中的代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        List<String> list = new ArrayList<>();
        list.add("H");
        list.add("e");
        list.add("l");
        list.add("l");
        list.add("o");
        list.add(" ");
        list.add("W");
        list.add("o");
        list.add("r");
        list.add("l");
        list.add("d");

        Observable.fromIterable(list);

    }
}复制代码

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

Again above example:

list.stream(); // Java8

Observable.fromIterable(list); // Android复制代码

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

list.stream()
       .forEach(s-> System.out.print(s)); // Java8

Observable.fromIterable(list)
        .forEach(s-> Log.i("Android",s)); // Android

Java8 的输出:
     Hello World
Android 的输出:
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: H
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: e
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: l
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: l
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: o
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: 
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: W
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: o
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: r
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: l
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: d复制代码

是时候来比较下这俩了。

list.stream().forEach(s-> System.out.print(s)); // Java8

Observable.fromIterable(list).forEach(s-> Log.i("Android",s)); // Android复制代码

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

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

In code:

list.stream()
        .filter(s -> !s.equals("l"))
        .forEach(s-> System.out.print(s)); //Java8

Observable.fromIterable(list)
        .filter(s->!s.equals("l"))
        .forEach(s-> Log.i("Android",s)); // Android

输出 in Java8: 
     Heo Word

输出 In Android:
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: H
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: e
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: o
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: 
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: W
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: o
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: r
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: d复制代码

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

3. RxJava2 的 Android 示例:

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

如图所示:

Android 代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Integer[] data = {1,2,3,4};

    Observable.fromArray(data)
            .map(value->value*value)
            .forEach(value-> Log.i("Android",value+""));
}复制代码

输出:
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(value->value*value)复制代码

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

.forEach(value-> Log.i("Android",value+""));复制代码

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

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

Observable.fromArray(data)
        .map(value->value*value)
        .map(value-> Integer.toString(value))
        .forEach(string-> Log.i("Android",string));复制代码

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

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

示例:

我在一个项目中使用了 OnBoarding 界面,根据 UI 设计须要在每一个 OnBoarding 界面上显示点点,以下图所示:


若是你观察得很仔细的话,能够看到我须要将选定的界面对应的点设置成黑色。

命令式编程的代码:

private void setDots(int position) {
    for (int i = 0; i < mCircleImageViews.length; i++) {
        if (i == position)
            mCircleImageViews[i].setImageResource(R.drawable.white_circle_solid_on_boarding);
        else
            mCircleImageViews[i].setImageResource(R.drawable.white_circle_outline_on_boarding);
    }
}复制代码

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

public void setDots(int position) {

    Observable.fromIterable(circleImageViews)
            .subscribe(imageView ->
                    imageView.setImageResource(R.drawable.white_circle_outline_on_boarding));
    circleImageViews.get(position)
            .setImageResource(R.drawable.white_circle_solid_on_boarding);

}复制代码

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

或者,

public void setDots(int position) {

        Observable.range(0, circleImageViews.size())
                .filter(i->i!=position)
                .subscribe(i->circleImageViews.get(i).setImageResource(R.drawable.white_circle_outline_on_boarding)));
        circleImageViews.get(position)
                .setImageResource(R.drawable.white_circle_solid_on_boarding);
}复制代码

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

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

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

为了让你们能够在现有的代码上轻松开始使用 Rx,我写了几个小技巧。

  1. 若是代码中有循环的话,用 Observable 替换
for (int i = 0; i < 10; i++) {

}

==>

Observable.range(0,10);复制代码
  1. 若是代码中有 if 语句的话,用 Rx 中的 filter 替换
for (int i = 0; i < 10; i++) {
    if(i%2==0){
        Log.i("Android", "Even");
    }
}

==>

Observable.range(0,10)
        .filter(i->i%2==0)
        .subscribe(value->Log.i("Android","Event :"+value));复制代码
  1. 若是须要把一些数据转换为另外一种格式,能够用 map 实现
public class User {
    String username;
    boolean status;

    public User(String username, boolean status) {
        this.username = username;
        this.status = status;
    }
}

List<User> users = new ArrayList<>();
users.add(new User("A",false));
users.add(new User("B",true));
users.add(new User("C",true));
users.add(new User("D",false));
users.add(new User("E",false));

for (User user : users) {
    if(user.status){
        user.username = user.username+ "Online";
    }else {
        user.username = user.username+ "Offline";
    }
}复制代码

在 Rx 中,有不少方法实现上述代码。

使用两个流:

Observable.fromIterable(users)
        .filter(user -> user.status)
        .map(user -> user.username + " Online")
        .subscribe(user -> Log.i("Android", user.toString()));
Observable.fromIterable(users)
        .filter(user -> !user.status)
        .map(user -> user.username + " Offline")
        .subscribe(user -> Log.i("Android", user.toString()));复制代码

在 map 中使用 if else :

Observable.fromIterable(users)
        .map(user -> {
            if (user.status) {
                user.username = user.username + " Online";
            } else {
                user.username = user.username + " Offline";
            }
            return user;
        })
        .subscribe(user -> Log.i("Android", user.toString()));复制代码
  1. 若是代码中有嵌套的循环:
for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        System.out.print("j ");
    }
    System.out.println("i");
}

==>

Observable.range(0, 10)
        .doAfterNext(i-> System.out.println("i"))
        .flatMap(integer -> Observable.range(0, 10))
        .doOnNext(i -> System.out.print("j "))
        .subscribe();复制代码

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

总结:

同志们干得好!今天咱们学 Rx Android 学得很开心。咱们从图画开始,而后使用了 Java8 的流(Stream)。以后将 Java8 的流转换到 RxJava 2 Android 的 Observable。再以后,咱们看到了实际项目中的示例而且展现了在现有的项目中如何开始使用 Rx。最后,我展现了一些转换到 Rx 的技巧:把循环用 forEach 替换,把 if 换成 filter,用 map 进行数据转化,用 flatmap 代替嵌套的循环。下篇文章: Dialogue between Rx Observable and a Developer (Me) [ Android RxJava2 ] ( What the hell is this ) Part5.

但愿大家开心,同志们再见!

代码:

  1. Water Stream Example(示例:水流)
  2. HelloWorldStream using Java8 Stream API(示例:Java8 Stream 初体验)
  3. HelloWorldStream using Rx Java2 Android(示例:RxJava2 Android 初体验) | project level gradle | app level gradle
  4. ArrayOfIntegers using Rx Java2 Android(示例:用 RxJava2 Android 操做整数数组) | project level gradle | app level gradle

对于其余全部示例,您可使用文章中的片断。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOSReact前端后端产品设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划

相关文章
相关标签/搜索