In-depth explanation of RxJava (two: operators)

In the first blog, I covered some basics of RxJava and also introduced the map() operator. Of course, if you don't want to use RxJava, I'm not surprised at all, after all, I've only come into contact with it. After reading this blog, I believe that you will definitely want to use RxJava in your project immediately. This blog will introduce many operators in RxJava. The power of RxJava comes from the operators it defines.

Let's look at an example first:

Preparations

Suppose I have a method
that returns a list of website URLs based on an input string (aha, search engines)
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 last blog, we might write the following code:
query("Hello, world!")  
    .subscribe(urls -> {  
        for (String url : urls) {  
            System.out.println(url);  
        }  
    });

This kind of code is of course intolerable, because the above code deprives us of the ability to change the data flow. Once we want to change every URL, we can only do it in Subscriber. We're not using such a cool map() operator! ! !

Of course, I can use the map operator. The input of the map is a list of urls. When processing, I still have to traverse for each, which is also very painful.

Fortunately, there is also the Observable.from() method, which takes a collection as input and outputs one element at a time to the subscriber:
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 has been removed, the code still looks messy. Not only does multiple nested subscriptions look ugly and hard to modify, but it also breaks some RxJava features that we haven't covered yet.

The improvement

savior is coming, he is flatMap().
Observable.flatMap() takes the output of one Observable as input and outputs another Observable. 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));

I've posted the entire function code here to make it easier for you to understand what's going on, using lambdas 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 to understanding flatMap is that the new Observable output by flatMap is exactly what we want to receive in Subscriber. Now the Subscriber no longer receives a List<String>, but rather a list of individual strings, just like the output of Observable.from().

This part was also the most difficult part to understand when I first learned RxJava. Once I suddenly realized it, many questions about RxJava were solved together.

Could be better

flatMap() couldn't be better, it can return any Observable object it wants.
For example the following method:
// Returns the title of the website, or null if 404  
Observable<String> getTitle(String URL);

Continuing the previous example, now I don't want to print the URL, but 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 easily 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));

Use lambda:
query("Hello, world!")  
    .flatMap(urls -> Observable.from(urls))  
    .flatMap(url -> getTitle(url))  
    .subscribe(title -> System.out.println(title));

Does it feel incredible? I was able to combine multiple independent methods that return Observable objects! Awesome!
Not only that, I also combined the calls of the two APIs into one chained call. We can chain as many API calls as we want. 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 that needs to be displayed. Here we have successfully avoided callback hell (multi-layered nested callbacks that make the code difficult to read and maintain). Now all the logic is wrapped into this simple reactive call.

Rich operators

So far, we have touched two operators, there are more operators in RxJava, so how can 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 null values ​​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 the input, and filters out those that do not satisfy the check condition.

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 up to the specified 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 each time, like saving the title here.

See how easy it is to manipulate the data flow here. You can add as many operations as you want without cluttering your code.

RxJava contains a large number of operators. The number of operators is a bit scary, but it's worth taking a look at each one so you can see what operators are available. It may take some time to understand these operators, but once you do, you fully grasp the power of RxJava.

You can even write custom operators! This blog is not going to include custom operators, if you want to, just google it.

how do you feel?

Well, you're a skeptic and still hard to convince, so why should you care about these operators?

Because operators allow you to do anything with data streams.

Complicated logic can be accomplished by chaining together a series of operators. Code is broken down into a series of composable pieces. That's the beauty of reactive functional programming. The more you use it, the more it will change your programming thinking.

Also, RxJava makes the way we work with data simpler. In the last example, we called two APIs, processed the data returned by the APIs, and saved them to disk. But our Subscriber doesn't know that, it just thinks it's receiving an Observable<String> object. Good encapsulation also brings the convenience of coding!


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.

Original link

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327101117&siteId=291194637