Operators are used to solve the problem of transforming Observable objects. Operators are used to modify events emitted by Observable between Observable and the final Subscriber. RxJava provides many useful operators.
For example, the map operator is used to convert one event into another.
Observable.just("Hello, world!") .map(new Func1<String, String>() { @Override public String call(String s) { return s + " -Dan"; } }) .subscribe(s -> System.out.println(s)); Using lambda can be simplified to
Observable.just("Hello, world!") .map(s -> s + " -Dan") .subscribe(s -> System.out.println(s));Isn't it cool? The map() operator is used to transform the Observable object. The map operator returns an Observable object, so that a chain call can be implemented, and the map operator is used multiple times on an Observable object, and finally the simplest data is passed to the Subscriber object.
map operator advanced
What's more interesting about the map operator is that it doesn't have to return the type returned by the Observable object. You can use the map operator to return an observable object that emits the new data type.
For example, in the above example, subscriber does not care about the returned string, but wants the hash value of the string.
Observable.just("Hello, world!") .map(new Func1<String, Integer>() { @Override public Integer call(String s) { return s.hashCode(); } }) .subscribe(i -> System.out.println(Integer.toString(i))); Very interesting, right? Our initial Observable returns a string, while the final Subscriber receives an Integer. Of course, using lambda can further simplify the code:
Observable.just("Hello, world!") .map(s -> s.hashCode()) .subscribe(i -> System.out.println(Integer.toString(i))); As mentioned earlier, the less things Subscribe does, the better. Let's add another map operator.
Observable.just("Hello, world!") .map(s -> s.hashCode()) .map(i -> Integer.toString(i)) .subscribe(s -> System.out.println(s)); Not convinced?
Do you think our example is too simple to convince you? You need to understand the following two points:
1. Observable and Subscriber can do anything
Observable can be a database query, and Subscriber is used to display query results; Observable can be a click event on the screen, and Subscriber is used to respond to click events; Observable can be a network request, and Subscriber is used to display request results.
2. Observable and Subscriber are independent of the intermediate transformation process.
Any number of maps can be added or decreased between Observable and Subscriber. The entire system is highly combinable, and operating data is a very simple process.
Example
1. Preparation
Suppose I have a method like this:
This method returns a website's url list based on the input string (ahha, search engine)
Observable<List<String>> query(String text);
Now I want to build a robust system that can query strings and display the results. Based on the content of the previous blog, we may write the following code:
query("Hello, world!") .subscribe(urls -> { for (String url : urls) { System.out.println(url); } });Of course, this kind of code cannot be tolerated, because the above code has caused us to lose the ability to change the data flow. Once we want to change each URL, we can only do it in Subscriber. We didn't use such a cool map() operator! ! !
Of course, I can use the map operator. The input of the map is the urls list. When processing, it still needs to traverse each, which is also very painful.
Fortunately, there is also the Observable.from() method, which receives a collection as input and then outputs an element to the subscriber at a time:
Observable.from("url1", "url2", "url3") .subscribe(url -> System.out.println(url)); Let's use this method to the scene just now:
query("Hello, world!") .subscribe(urls -> { Observable.from(urls) .subscribe(url -> System.out.println(url)); });
Although the for each loop is removed, the code still looks messy. Multiple nested subscriptions not only look ugly and difficult to modify, but more seriously, it will destroy some of the features of RxJava that we have not yet mentioned.
2. Improvement
The savior is here, he is flatMap().
Observable.flatMap() receives the output of an Observable as input and outputs another Observable at the same time. Look directly at the code:
query("Hello, world!") .flatMap(new Func1<List<String>, Observable<String>>() { @Override public Observable<String> call(List<String> urls) { return Observable.from(urls); } }) .subscribe(url -> System.out.println(url)); Here I posted the entire function code to facilitate you to understand what is going on. Using lambda can greatly simplify the code length:
query("Hello, world!") .flatMap(urls -> Observable.from(urls)) .subscribe(url -> System.out.println(url));Does flatMap() look weird? Why does it return another Observable? The key point in understanding flatMap is that the new Observable output from flatMap is exactly what we want to receive in Subscriber. Now Subscriber no longer receives List<String>, but instead receives some single strings of columns, just like the output of Observable.from().
This part is also the most difficult part when I first learned RxJava. Once I suddenly realized it, many of the questions in RxJava were solved.
3. It can be better
flatMap() is really not a better idea, it can return any Observable object it wants to return.
For example, the following method:
// Return the title of the website, and if it is 404, return null Observable<String> getTitle(String URL);
Following the previous example, now I don't want to print the URL anymore, but instead print the title of each website I receive. The problem is that my method can only pass in one URL at a time, and the return value is not a String, but an Observabl object that outputs a String. Using flatMap() can simply solve this problem.
query("Hello, world!") .flatMap(urls -> Observable.from(urls)) .flatMap(new Func1<String, Observable<String>>() { @Override public Observable<String> call(String url) { return getTitle(url); } }) .subscribe(title -> System.out.println(title)); 4. Use lambda:
query("Hello, world!") .flatMap(urls -> Observable.from(urls)) .flatMap(url -> getTitle(url)) .subscribe(title -> System.out.println(title)); Doesn’t it feel incredible? I can actually combine multiple independent methods to return Observable objects together! Very handsome!
More than that, I also combined the calls of two APIs into a chained call. We can link as many API calls as many as possible. Everyone should know how painful it is to synchronize all API calls and then combine the callback results of all API calls into the data to be displayed. Here we successfully avoided callback hell (multi-layer nested callbacks, making the code difficult to read and maintain). Now all logic is wrapped into this simple responsive call.
5. Rich operators <br /> So far, we have come into contact with two operators, and there are more operators in RxJava, so how do we use other operators to improve our code?
getTitle() returns null if the url does not exist. We don't want to output "null", then we can filter out the null value from the returned title list!
query("Hello, world!") .flatMap(urls -> Observable.from(urls)) .flatMap(url -> getTitle(url)) .filter(title -> title != null) .subscribe(title -> System.out.println(title));filter() outputs the same elements as input and filters out those that do not meet the check criteria.
If we only want up to 5 results:
query("Hello, world!") .flatMap(urls -> Observable.from(urls)) .flatMap(url -> getTitle(url)) .filter(title -> title != null) .take(5) .subscribe(title -> System.out.println(title));take() outputs the maximum number of results.
If we want to save each title to disk before printing:
query("Hello, world!") .flatMap(urls -> Observable.from(urls)) .flatMap(url -> getTitle(url)) .filter(title -> title != null) .take(5) .doOnNext(title -> saveTitle(title)) .subscribe(title -> System.out.println(title));doOnNext() allows us to do some extra things before outputting an element at a time, like saving the title here.
Is it easy to see how easy it is to operate data flow here? You can add as many operations as you like and don't mess up your code.
RxJava contains a large number of operators. The number of operators is a bit scary, but it is worth checking out one by one so that you can know which operators can be used. It may take some time to understand these operators, but once you understand, you will fully grasp the power of RxJava.
You can even write custom operators! This blog does not intend to customize the operator. If you want, please google it yourself.
How do you feel?
Well, you're a skeptic and it's hard to convince, so why do you care about these operators?
Because operators allow you to do anything to the data stream.
Linking a series of operators can accomplish complex logic. The code is broken down into a series of snippets that can be combined. This is the charm of reactive function programming. The more you use it, the more you will change your programming thinking.
In addition, RxJava also makes it easier for us to process data. In the last example, we call two APIs, process the data returned by the API, and save it to disk. But our Subscriber doesn't know this, it just thinks it is receiving an Observable<String> object. Good packaging also brings coding convenience!
In the third part, I will introduce some other cool features of RxJava, such as error handling and concurrency, which are not directly used to process data.