过滤Observables

在上一章中,我们学习了使用RxJava创建一个Android工程以及如何创建一个可观测的列表来填充RecyclerView。我们现在知道了如何从头、从列表、从一个已存在的传统Java函数来创建Observable。

这一章中,我们将研究可观测序列的本质:过滤。我们将学到如何从发射的Observable中选取我们想要的值,如何获取有限个数的值,如何处理溢出的场景,以及更多的有用的技巧。

过滤序列

RxJava让我们使用filter()方法来过滤我们观测序列中不想要的值,在上一章中,我们在几个例子中使用了已安装的应用列表,但是我们只想展示以字母C开头的已安装的应用该怎么办呢?在这个新的例子中,我们将使用同样的列表,但是我们会过滤它,通过把合适的谓词传给filter()函数来得到我们想要的值。

上一章中loadList()函数可以改成这样:

 
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
private void loadList(List<AppInfo> apps) {
    mRecyclerView.setVisibility(View.VISIBLE);
    Observable.from(apps)
            .filter((appInfo) ->
            appInfo.getName().startsWith("C"))
            .subscribe(new Observable<AppInfo>() {
 
                @Override
                public void onCompleted() {
                    mSwipeRefreshLayout.setRefreshing(false);
                }
 
                @Override
                public void onError(Throwable e) {
                    Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
                    mSwipeRefreshLayout.setRefreshing(false);
                }
 
                @Override
                public void onNext(AppInfo appInfo) {
                    mAddedApps.add(appInfo);
                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
                }
            });
}
 

我们从上一章中的loadList()函数中添加下面一行:

 
1
2
.fliter((appInfo -> appInfo.getName().startsWith("C"))
 

创建Observable完以后,我们从发出的每个元素中过滤掉开头字母不是C的。为了让这里更清楚一些,我们用Java 7的语法来实现:

 
1
2
3
4
5
6
7
.filter(new Func1<AppInfo,Boolean>(){
    @Override
    public Boolean call(AppInfo appInfo){
        return appInfo.getName().startsWith("C");
    }
})
 

我们传一个新的Func1对象给filter()函数,即只有一个参数的函数。Func1有一个AppInfo对象来作为它的参数类型并且返回Boolean对象。只要条件符合filter()函数就会返回true。此时,值会发射出去并且所有的观察者都会接收到。

正如你想的那样,从一个我们得到的可观测序列中创建一个我们需要的序列filter()是很好用的。我们不需要知道可观测序列的源或者为什么发射这么多不同的数据。我们只是想要这些元素的子集来创建一个可以在应用中使用的新序列。这种思想促进了我们编码中的分离性与抽象性。

filter()函数最常用的用法之一时过滤null对象:

 
1
2
3
4
5
6
7
.filter(new Func1<AppInfo,Boolean>(){
    @Override
    public Boolean call(AppInfo appInfo){
        return appInfo != null;
    }
})
 

这看起来简单,对于简单的事情有许多模板代码,但是它帮我们免去了在onNext()函数调用中再去检测null值,让我们把注意力集中在应用业务逻辑上。

下图展示了过滤出的C字母开头的已安装的应用列表。

获取我们需要的数据

当我们不需要整个序列时,而是只想取开头或结尾的几个元素,我们可以用take()takeLast()

Take

如果我们只想要一个可观测序列中的前三个元素那将会怎么样,发射它们,然后让Observable完成吗?take()函数用整数N来作为一个参数,从原始的序列中发射前N个元素,然后完成:

 
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
private void loadList(List<AppInfo> apps) {
    mRecyclerView.setVisibility(View.VISIBLE);
    Observable.from(apps)
            .take(3)
            .subscribe(new Observable<AppInfo>() {
 
                @Override
                public void onCompleted() {
                    mSwipeRefreshLayout.setRefreshing(false);
                }
 
                @Override
                public void onError(Throwable e) {
                    Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
                    mSwipeRefreshLayout.setRefreshing(false);
                }
 
                @Override
                public void onNext(AppInfo appInfo) {
                    mAddedApps.add(appInfo);
                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
                }
            });
}
 

下图中展示了发射数字的一个可观测序列。我们对这个可观测序列应用take(2)函数,然后我们创建一个只发射可观测源的第一个和第二个数据的新序列。

TakeLast

如果我们想要最后N个元素,我们只需使用takeLast()函数:

 
1
2
3
4
Observable.from(apps)
        .takeLast(3)
        .subscribe(https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.);
 

正如听起来那样不值一提,重点注意takeLast()函数由于用一组有限的发射数的本质使得它仅可用于完成的序列。

下图中展示了如何从可观测源中发射最后一个元素来创建一个新的序列:

下图中展示了我们在已安装的应用列表使用take()takeLast()函数后发生的结果:

有且仅有一次

一个可观测序列会在出错时重复发射或者被设计成重复发射。distinct()distinctUntilChanged()函数可以方便的让我们处理这种重复问题。

Distinct

如果我们想对一个指定的值仅处理一次该怎么办?我们可以对我们的序列使用distinct()函数去掉重复的。就像takeLast()一样,distinct()作用于一个完整的序列,然后得到重复的过滤项,它需要记录每一个发射的值。如果你在处理一大堆序列或者大的数据记得关注内存使用情况。

下图展示了如何在一个发射1和2两次的可观测源上创建一个无重的序列:

为了创建我们例子中序列,我们将使用我们至今已经学到的几个方法:
take():它有一小组的可识别的数据项。
repeat():创建一个有重复的大的序列。

然后,我们将应用distinct()函数来去除重复。

注意

我们用程序实现一个重复的序列,然后过滤出它们。这听起来时不可思议的,但是为了实现这个例子来使用我们至今为止已学习到的东西则是个不错的练习。

 
1
2
3
4
Observable<AppInfo> fullOfDuplicates = Observable.from(apps)
    .take(3)
    .repeat(3);
 

fullOfDuplicates变量里把我们已安装应用的前三个重复了3次:有9个并且许多重复的。然后,我们使用distinct():

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fullOfDuplicates.distinct()
            .subscribe(new Observable<AppInfo>() {
 
                @Override
                public void onCompleted() {
                    mSwipeRefreshLayout.setRefreshing(false);
                }
 
                @Override
                public void onError(Throwable e) {
                    Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
                    mSwipeRefreshLayout.setRefreshing(false);
                }
 
                @Override
                public void onNext(AppInfo appInfo) {
                    mAddedApps.add(appInfo);
                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
                }
            });
}
 

结果,很明显,我们得到:

DistinctUntilsChanged

如果在一个可观测序列发射一个不同于之前的一个新值时让我们得到通知这时候该怎么做?我们猜想一下我们观测的温度传感器,每秒发射的室内温度:

 
1
2
21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.21°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.22°https://github.com/yuxingxin/RxJava-Essentials-CN/raw/master.
 

每次我们获得一个新值,我们都会更新当前正在显示的温度。我们出于系统资源保护并不想在每次值一样时更新数据。我们想忽略掉重复的值并且在温度确实改变时才想得到通知。ditinctUntilChanged()过滤函数能做到这一点。它能轻易的忽略掉所有的重复并且只发射出新的值。

下图用图形化的方式展示了我们如何将distinctUntilChanged()函数应用在一个存在的序列上来创建一个新的不重复发射元素的序列。

First and last

下图展示了如何从一个从可观测源序列中创建只发射第一个元素的序列。

first()方法和last()方法很容易弄明白。它们从Observable中只发射第一个元素或者最后一个元素。这两个都可以传Func1作为参数,:一个可以确定我们感兴趣的第一个或者最后一个的谓词:

下图展示了last()应用在一个完成的序列上来创建一个仅仅发射最后一个元素的新的Observable。

first()last()相似的变量有:firstOrDefault()lastOrDefault().这两个函数当可观测序列完成时不再发射任何值时用得上。在这种场景下,如果Observable不再发射任何值时我们可以指定发射一个默认的值

Skip and SkipLast

下图中展示了如何使用skip(2)来创建一个不发射前两个元素而是发射它后面的那些数据的序列。

skip()skipLast()函数与take()takeLast()相对应。它们用整数N作参数,从本质上来说,它们不让Observable发射前N个或者后N个值。如果我们知道一个序列以没有太多用的“可控”元素开头或结尾时我们可以使用它。

下图与前一个场景相对应:我们创建一个新的序列,它会跳过后面两个元素从源序列中发射剩下的其他元素。

ElementAt

如果我们只想要可观测序列发射的第五个元素该怎么办?elementAt()函数仅从一个序列中发射第n个元素然后就完成了。

如果我们想查找第五个元素但是可观测序列只有三个元素可供发射时该怎么办?我们可以使用elementAtOrDefault()。下图展示了如何通过使用elementAt(2)从一个序列中选择第三个元素以及如何创建一个只发射指定元素的新的Observable。

Sampling

让我们再回到那个温度传感器。它每秒都会发射当前室内的温度。说实话,我们并不认为温度会变化这么快,我们可以使用一个小的发射间隔。在Observable后面加一个sample(),我们将创建一个新的可观测序列,它将在一个指定的时间间隔里由Observable发射最近一次的数值:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Observable<Integer> sensor = [...]
 
sensor.sample(30,TimeUnit.SECONDS)
    .subscribe(new Observable<Integer>() {
 
        @Override
        public void onCompleted() {
 
        }
 
        @Override
        public void onError(Throwable e) {
 
        }
 
        @Override
        public void onNext(Integer currentTemperature) {
            updateDisplay(currentTemperature)
        }
    });
 

例子中Observable将会观测温度Observable然后每隔30秒就会发射最后一个温度值。很明显,sample()支持全部的时间单位:秒,毫秒,天,分等等。

下图中展示了一个间隔发射字母的Observable如何采样一个发射数字的Observable。Observable的结果将会发射每个已发射字母的最后一组数据:1,4,5.

如果我们想让它定时发射第一个元素而不是最近的一个元素,我们可以使用throttleFirst()

Timeout

假设我们工作的是一个时效性的环境,我们温度传感器每秒都在发射一个温度值。我们想让它每隔两秒至少发射一个,我们可以使用timeout()函数来监听源可观测序列,就是在我们设定的时间间隔内如果没有得到一个值则发射一个错误。我们可以认为timeout()为一个Observable的限时的副本。如果在指定的时间间隔内Observable不发射值的话,它监听的原始的Observable时就会触发onError()函数。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Subscription subscription = getCurrentTemperature()
    .timeout(2,TimeUnit.SECONDS)
    .subscribe(new Observable<Integer>() {
 
        @Override
        public void onCompleted() {
 
        }
 
        @Override
        public void onError(Throwable e) {
            Log.d("RXJAVA","You should go check the sensor, dude");
        }
 
        @Override
        public void onNext(Integer currentTemperature) {
            updateDisplay(currentTemperature)
        }
    });
 

sample()一样,timeout()使用TimeUnit对象来指定时间间隔。

下图中展示了一旦Observable超过了限时就会触发onError()函数:因为超时后它才到达,所以最后一个元素将不会发射出去。

Debounce

debounce()函数过滤掉由Observable发射的速率过快的数据;如果在一个指定的时间间隔过去了仍旧没有发射一个,那么它将发射最后的那个。

就像sample()timeout()函数一样,debounce()使用TimeUnit对象指定时间间隔。

下图展示了多久从Observable发射一次新的数据,debounce()函数开启一个内部定时器,如果在这个时间间隔内没有新的数据发射,则新的Observable发射出最后一个数据:

总结

这一章中,我们学习了如何过滤一个可观测序列。我们现在可以使用filter()skip(),和sample()来创建我们想要的Observable。

下一章中,我们将学习如何转换一个序列,将函数应用到每个元素,给它们分组和扫描来创建我们所需要的能完成目标的特定Observable。

RxJava开发精要4 – Observables过滤的更多相关文章

  1. RxJava开发精要5 – Observables变换

    原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发者头条享有独家转 ...

  2. RxJava开发精要6 – Observables组合

    原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发者头条享有独家转 ...

  3. RxJava开发精要2-为什么是Observables?

    原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发者头条享有独家转 ...

  4. RxJava开发精要6 - 组合Observables

    原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发人员头条享有独家 ...

  5. RxJava开发精要3-向响应式世界问好

    原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发者头条享有独家转 ...

  6. RxJava开发精要1-从.NET到RxJava

    原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发者头条享有独家转 ...

  7. RxJava开发精要8 – 与REST无缝结合-RxJava和Retrofit

    原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发者头条享有独家转 ...

  8. RxJava开发精要7 – Schedulers-解决Android主线程问题

    原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发者头条享有独家转 ...

  9. MongoDB管理与开发精要 书摘

    摘自:<MongoDB管理与开发精要>         性能优化 创建索引 限定返回结果条数 只查询使用到的字段,而不查询所有字段 采用capped collection 采用Server ...

随机推荐

  1. ionic+cordova+angularJs监听刷新

    普通的js返回并刷新这里就不多说了,百度就有很多方法. 下面说的是使用了angularjs.ionic开发的一个手机app中我使用的返回上一页并刷新的方法. 场景:回复的页面是单独的,点击保存回复后会 ...

  2. [转载]sql server 等待类型

    下表列出各任务所遇到的等待类型. 等待类型 说明 ASYNC_DISKPOOL_LOCK 当尝试同步并行的线程(执行创建或初始化文件等任务)时出现. ASYNC_IO_COMPLETION 当某任务正 ...

  3. ios code style

    注释 建议使用VVDocumenter插件 多行注释 格式: /** 注释内容 */ 单行注释 格式: ///在对文件.类.函数进行注释时推荐使用多行注释,在函数体内对代码块进行注释时,使用单行注释 ...

  4. android开发 锁屏 真正的锁屏,是go锁屏那种。

    想做个锁屏界面很久了,最近一周,历经千辛万苦,越过种种挫折,终于完美实现了这一要求,在此将锁屏思路分享出来. 注意:这不是什么一键锁屏,是类似“go锁屏”那样的锁屏界面. 准备:本程序共需要 两个ac ...

  5. Express安装与调试

    Express 是基于Node.Js平台,快速.开放.极简的 web 开发框架. 1.安装 Express的安装通过cmd来进行,过程如下: 首先,先在本地建立一个项目文件夹,取名Nodejs. 然后 ...

  6. [leetcode] 400. Nth Digit

    https://leetcode.com/contest/5/problems/nth-digit/ 刚开始看不懂题意,后来才理解是这个序列连起来的,看一下第几位是几.然后就是数,1位数几个,2位数几 ...

  7. HTML5入门篇

    ---- HTML5简介 HTML5 是用于取代1999年所制定的 HTML 4.01 和 XHTML 1.0 标准的 HTML 标准版本,现在仍处于发展阶段,但大部分浏览器已经支持某些 HTML5 ...

  8. Huffman Coding 哈夫曼编码

    作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4096079.html 使用优先队列实现,需要注意以下几点: 1.在使用priority_qu ...

  9. mysql Error Handling and Raising in Stored Procedures

    MySQL的存储过程错误捕获方式和Oracle的有很大的不同. MySQL中可以使用DECLARE关键字来定义处理程序.其基本语法如下: DECLARE handler_type HANDLER FO ...

  10. 制作Mac OS X Mavericks 安装U盘

    1. 8G+ U盘一个. 2. App Store 下载Maverics到本地(默认会下载到Applications) 2. 打开Mac OS 磁盘工具(Disk Utility),左侧选中U盘,在右 ...