RxJava学习 - 8. Combining Observables

Merging

可以把两个或者多个Observable实例合并成一个Observable。合并后的Observable同时订阅所有的源。

Observable.merge() and mergeWith()

如果有两个到四个Observable源,可以使用Observable.merge()工厂合并:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        Observable<String> source2 =
                Observable.just("Zeta", "Eta", "Theta");
        Observable.merge(source1, source2)
                .subscribe(i -> System.out.println("RECEIVED: " + i));    
    }
}

或者,使用mergeWith():

source1.mergeWith(source2)
        .subscribe(i -> System.out.println("RECEIVED: " + i));

Observable.merge()和mergeWith()将同时订阅所有的源,但是如果他们都是cold,并且都使用同一个线程,会按顺序发射。如果你想明确地按顺序发射每个Observable的元素,应该使用Observable.concat()。
如果你的源不止4个,可以使用Observable.mergeArray()。因为RxJava 2.0不能访问@SafeVarargs注解,你会得到类型安全警告:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source1 = Observable.just("Alpha", "Beta");
        Observable<String> source2 = Observable.just("Gamma", "Delta");
        Observable<String> source3 = Observable.just("Epsilon", "Zeta");
        Observable<String> source4 = Observable.just("Eta", "Theta");
        Observable<String> source5 = Observable.just("Iota", "Kappa");

        Observable.mergeArray(source1, source2, source3, source4, source5)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
    }
}

也可以给Observable.merge()传Iterable<Observable>。这样做是类型安全的:

import io.reactivex.Observable;
import java.util.Arrays;
import java.util.List;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source1 = Observable.just("Alpha", "Beta");
        Observable<String> source2 = Observable.just("Gamma", "Delta");
        Observable<String> source3 = Observable.just("Epsilon", "Zeta");
        Observable<String> source4 = Observable.just("Eta", "Theta");
        Observable<String> source5 = Observable.just("Iota", "Kappa");
        
        List<Observable<String>> sources =
                Arrays.asList(source1, source2, source3, source4, source5);
        Observable.merge(sources)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
    }
}

无限的Observables也可以使用Observable.merge()。因为它会订阅全部Observables,在他们有效的时候就发射:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        //emit every second
        Observable<String> source1 = Observable.interval(1, TimeUnit.SECONDS)
                .map(l -> l + 1) // emit elapsed seconds
                .map(l -> "Source1: " + l + " seconds");
        //emit every 300 milliseconds
        Observable<String> source2 =
                Observable.interval(300, TimeUnit.MILLISECONDS)
                        .map(l -> (l + 1) * 300) // emit elapsed milliseconds
                        .map(l -> "Source2: " + l + " milliseconds");
        //merge and subscribe
        Observable.merge(source1, source2)
                .subscribe(System.out::println);
        //keep alive for 3 seconds
        sleep(3000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

flatMap()

更有用、危险的是flatMap(),你应该花时间仔细理解它。它执行动态Observable.merge(),接受每个emission,映射成一个Observable。然后,它从返回的Observables合并这些emissions,放进一个流。
flatMap()的最简单的应用是映射一个emission到很多emissions。比如,我们想映射来自Observable的字符。我们可以使用flatMap()指定一个Function<T, Observable>,把每个字符串映射到一个Observable,它将发射字母。注意,映射后的Observable能发射任何类型R,和源T不同。在这里例子里,恰好和源相同:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        source.flatMap(s -> Observable.fromArray(s.split("")))
                .subscribe(System.out::println);
    }
}

输出是

A
l
p
h
a
B
e
t
a
......

我们接受五个字符串,映射成每个的字母。我们使用每个字符串的split()方法,传给它一个空参数,这样分开每个字符。它返回的字符串数组包含全部字符,然后传给Observable.fromArray()发射每一个。flatMap()期望每个emission产生一个Observable,然后合并所有的返回的Observables,发射到一个流。
下来是另一个例子:接受一系列字符串值(每个都使用"/"级联),使用flatMap(),过滤留下只包含数字的字符串,然后转换成整数emissions:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("521934/2342/FOXTROT", "21962/12112/78886/TANGO",
                        "283242/4542/WHISKEY/2348562");
        source.flatMap(s -> Observable.fromArray(s.split("/")))
                .filter(s -> s.matches("[0-9]+")) //use regex to filter integers
                .map(Integer::valueOf)
                .subscribe(System.out::println);
    }
}

输出是

521934
2342
21962
12112
78886
283242
4542
2348562

就像Observable.merge(),也可以映射到无限的Observables,合并他们。例如,我们能发射整数值,使用flatMap()变成Observable.interval()。下面的例子,我们发射2、3、10和7,将产生interval Observables,分别在2秒、3秒、10秒和7秒的时候发射。这四个Observables被合并成一个流:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Integer> intervalArguments =
                Observable.just(2, 3, 10, 7);
        intervalArguments.flatMap(i ->
                Observable.interval(i, TimeUnit.SECONDS)
                        .map(t -> i + "s interval: " + ((t + 1) * i) + " seconds elapsed"))
                        .subscribe(System.out::println);
        sleep(12000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是

2s interval: 2 seconds elapsed
3s interval: 3 seconds elapsed
2s interval: 4 seconds elapsed
2s interval: 6 seconds elapsed
3s interval: 6 seconds elapsed
7s interval: 7 seconds elapsed
2s interval: 8 seconds elapsed
3s interval: 9 seconds elapsed
2s interval: 10 seconds elapsed
10s interval: 10 seconds elapsed
2s interval: 12 seconds elapsed
3s interval: 12 seconds elapsed

Observable.merge()只能访问固定数量的Observable源。而flatMap()可以动态增加新的Observable源。
上面的例子,如果给flatMap()发射一个0,会破坏Observable.interval()。可以使用if语句,如果是0就返回Observable.empty():

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Integer> secondIntervals =
                Observable.just(2, 0, 3, 10, 7);
        secondIntervals.flatMap(i -> {
            if (i == 0)
                return Observable.empty();
            else
                return Observable.interval(i, TimeUnit.SECONDS)
                        .map(t -> i + "s interval: " + ((t + 1) * i) + " seconds elapsed");
        }).subscribe(System.out::println);

        sleep(12000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

当然,更聪明的办法是在flatMap()之前使用filter()过滤掉0值。
flatMap()有很多变种。比如,我们能传入第二个参数,BiFunction<T, U, R>,关联原生的T值和每个flat-mapped U值,转换成R值:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        source.flatMap(s -> Observable.fromArray(s.split("")), (s, r) -> s + "-" + r)
                .subscribe(System.out::println);
    }
}

Concatenation

级联和合并类似,只是有一个细微的差别:它会顺序发射每个Observable的元素。直到当前的调用了onComplete(),才移到下一个Observable。不要用于无限的Observables。

Observable.concat() and concatWith()

下面的例子,有两个源发射字符串。可以使用Observable.concat(),第一个的发射完,再发射另一个:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        Observable<String> source2 =
                Observable.just("Zeta", "Eta", "Theta");
        Observable.concat(source1, source2)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
    }
}

上面的代码也可以使用concatWith():

source1.concatWith(source2)
        .subscribe(i -> System.out.println("RECEIVED: " + i));

下面,第一个Observable每秒发射,但是发射两次以后,调用onComplete()处置它。然后,第二个Observable开始发射,在5秒后程序退出:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        //emit every second, but only take 2 emissions
        Observable<String> source1 =
                Observable.interval(1, TimeUnit.SECONDS)
                        .take(2)
                        .map(l -> l + 1) // emit elapsed seconds
                        .map(l -> "Source1: " + l + " seconds");
        //emit every 300 milliseconds
        Observable<String> source2 =
                Observable.interval(300, TimeUnit.MILLISECONDS)
                        .map(l -> (l + 1) * 300) // emit elapsed milliseconds
                        .map(l -> "Source2: " + l + " milliseconds");
        Observable.concat(source1, source2)
                .subscribe(i -> System.out.println("RECEIVED: " + i));
        //keep application alive for 5 seconds
        sleep(5000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

concatMap()

concatMap()顺序合并每个映射的Observable,一次发射一个。当前的一个调用了onComplete(),就移到下一个。如果源产生的比concatMap()能发射的快,其他Observables只能排队。
前面的flatMap()例子如果关心顺序,更适合使用concatMap()。和前面的例子的输出相同:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        source.concatMap(s -> Observable.fromArray(s.split("")))
                .subscribe(System.out::println);
    }
}

Ambiguous

Observable.amb()接受一个Iterable<Observable>,发射第一个Observable的元素,其他的被处置(disposed of)。
如果你有相同数据或者事件的多个源,你想发射最快的一个,可以这样做。
这里,我们有两个interval源,使用Observable.amb()组合他们。如果一个每秒发射,另一个每300毫秒发射,后一个胜利:

import io.reactivex.Observable;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        //emit every second
        Observable<String> source1 =
                Observable.interval(1, TimeUnit.SECONDS)
                        .take(2)
                        .map(l -> l + 1) // emit elapsed seconds
                        .map(l -> "Source1: " + l + " seconds");
        //emit every 300 milliseconds
        Observable<String> source2 =
                Observable.interval(300, TimeUnit.MILLISECONDS)
                        .map(l -> (l + 1) * 300) // emit elapsed milliseconds
                        .map(l -> "Source2: " + l + " milliseconds");
        //emit Observable that emits first
        Observable.amb(Arrays.asList(source1, source2))
                .subscribe(i -> System.out.println("RECEIVED: " + i));
        //keep application alive for 5 seconds
        sleep(5000);        
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是

RECEIVED: Source2: 300 milliseconds
RECEIVED: Source2: 600 milliseconds
RECEIVED: Source2: 900 milliseconds
RECEIVED: Source2: 1200 milliseconds
RECEIVED: Source2: 1500 milliseconds
RECEIVED: Source2: 1800 milliseconds
......

也可以使用ambWith():

//emit Observable that emits first
source1.ambWith(source2).subscribe(i -> System.out.println("RECEIVED: " + i));

Zipping

Zipping允许你从每个Observable源接受一个emission,组合进一个emission。每个Observable能发射不同的类型,但是你能组合这些不同的类型的对象进一个emission。
下面的例子,如果我们有一个Observable和一个Observable,可以zip每个字符串和整数成为一个一对一的配对,使用lambda做级联:

import io.reactivex.Observable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        Observable<Integer> source2 = Observable.range(1,6);
        Observable.zip(source1, source2, (s, i) -> s + "-" + i)
                .subscribe(System.out::println);
    }
}

输出是

Alpha-1
Beta-2
Gamma-3
Delta-4
Epsilon-5

zip()接收Alpha和1,然后使用“-”做级联。然后,它接收Beta和2,然后做级联……来自一个Observable的emission必须等来自另一个Observable的emission做配对。如果一个调用了onComplete(),而另一个仍然有元素等着配对,那些元素被简单地抛弃。
也可以使用zipWith():

source1.zipWith(source2, (s,i) -> s + "-" + i)

Observable.zip()最多支持9个Observable实例。需过有更多的,可以传Iterable<Observable>或者使用zipArray()提供一个Observable[]。
如果一个或者多个源比其他源生产emissions的速度快,zip()把快的emissions放进队列等慢的源提供emissions。队列太大会产生性能问题。
如果你只关心每个源的最新的emission,可以使用combineLatest(),以后再讲。
使用Observable.zipIterable(),传一个Boolean类型的delayError参数,可以延迟错误,直到所有的源终止。传一个int类型的bufferSize提示每个源的期望的元素数量。有些情况下,你可以使用bufferSize在zip之前缓存他们来提升性能。
使用Observable.interval()的时候,Zipping也有助于放慢emissions。下面的代码,字符串每秒发射一个:

import io.reactivex.Observable;
import java.time.LocalTime;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> strings =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        Observable<Long> seconds =
                Observable.interval(1, TimeUnit.SECONDS);
        Observable.zip(strings, seconds, (s, l) -> s)
                .subscribe(s ->
                        System.out.println("Received " + s + " at " + LocalTime.now())
                );
        sleep(6000);        
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Combine latest

Observable.combineLatest()有点像zip(),但是,对于来自源的每个emission,它会立刻与其他源的最新的emission组合起来。它不会把每个源的未配对的emissions放进队列,而是缓存和配对最新的一个。
下面的例子,有两个interval Observables,一个每秒发射,另一个每300毫秒发射:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Long> source1 =
                Observable.interval(300, TimeUnit.MILLISECONDS);
        Observable<Long> source2 =
                Observable.interval(1, TimeUnit.SECONDS);
        Observable.combineLatest(source1, source2,
                (l1, l2) -> "SOURCE 1: " + l1 + " SOURCE 2: " + l2)
                .subscribe(System.out::println);

        sleep(3000);     
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是

SOURCE 1: 2 SOURCE 2: 0
SOURCE 1: 3 SOURCE 2: 0
SOURCE 1: 4 SOURCE 2: 0
SOURCE 1: 5 SOURCE 2: 0
SOURCE 1: 5 SOURCE 2: 1
SOURCE 1: 6 SOURCE 2: 1
SOURCE 1: 7 SOURCE 2: 1
SOURCE 1: 8 SOURCE 2: 1
SOURCE 1: 9 SOURCE 2: 1
SOURCE 1: 9 SOURCE 2: 2

source1每300毫秒发射,但是前两个emissions没有可配对的,因为source2还没有emission。一秒以后,source2发射了0,它的配对是source1的2(第三个emission)。source1然后发射3、4、5,而source2还是0,所以他们都配对了……

withLatestFrom()

withLatestfrom()映射每个T emission和其他Observables的最新值,组合他们,但是,它从每个其他Observables只接受一个:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Long> source1 =
                Observable.interval(300, TimeUnit.MILLISECONDS);
        Observable<Long> source2 =
                Observable.interval(1, TimeUnit.SECONDS);
        source2.withLatestFrom(source1, (l1,l2) -> "SOURCE 2: " + l1 + " SOURCE 1: " + l2)
                .subscribe(System.out::println);

        sleep(3000);
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是

SOURCE 2: 0 SOURCE 1: 2
SOURCE 2: 1 SOURCE 1: 5
SOURCE 2: 2 SOURCE 1: 9

source2每秒发射,source1每300毫秒发射。当你的source2调用withLatestFrom(),参数是source1,它将组合source1的最新的emission,而不关心以前的emissions。

Grouping

groupBy()接受一个lambda,把每个emission映射成一个key,返回Observable<GroupedObservable<K, T>>,它发射一个特殊类型的Observable叫GroupedObservable<K, T>。GroupedObservable根据key,把emissions分到分开的Observables组。
下面的例子,使用groupBy()根据字符串长度分组:

import io.reactivex.Observable;
import io.reactivex.observables.GroupedObservable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        Observable<GroupedObservable<Integer,String>> byLengths =
                source.groupBy(String::length);

        byLengths.flatMapSingle(GroupedObservable::toList)
                .subscribe(System.out::println);        
    }
}

如果在每个GroupedObservable上使用flatMap(),可以reduce或者collect那些common-key的emissions,因此会返回一个Single。所以我们使用flatMapSingle()。然后调用toList(),就可以发射分组后的列表了。
输出是

[Beta]
[Alpha, Gamma, Delta]
[Epsilon]

GroupedObservable有一个getKey()方法,可以返回key的值。如果想简单地级联每个GroupedObservable和key:

import io.reactivex.Observable;
import io.reactivex.observables.GroupedObservable;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon");
        Observable<GroupedObservable<Integer,String>> byLengths =
                source.groupBy(String::length);
        byLengths.flatMapSingle(grp ->
                grp.reduce("", (x, y) -> x.equals("") ? y : x + ", " + y)
                        .map(s -> grp.getKey() + ": " + s)
        ).subscribe(System.out::println); 
    }
}

输出是

4: Beta
5: Alpha, Gamma, Delta
7: Epsilon

注意:GroupedObservables是一个hot和cold Observable的怪异组合。他们不是cold,不会把错过的emissions重播给第二个Observer;但是,他们会缓存这些emissions,flush给第一个Observer,确保不丢失。如果你需要重播emissions,收集他们进一个列表,执行操作。可以使用caching operators,以后再讲这个。

猜你喜欢

转载自blog.csdn.net/weixin_43364172/article/details/84133240
今日推荐