UnaryOperator
当输入输出类型相同时,可以使用 UnaryOperator 函数接口。
class User2 {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String say(UnaryOperator<String> sayHello) {
return sayHello.apply(this.username);
}
}
public class LambdaDemo03 {
public static void main(String[] args) {
User2 user2 = new User2();
user2.setUsername("javaboy");
UnaryOperator<String> func = (username) -> "helloo " + username;
String say = user2.say(func);
System.out.println("say = " + say);
}
}
Predicate
Predicate 输入一个 T 类型的参数,输出一个 boolean 类型的值。
public class LambdaDemo04 {
public static void main(String[] args) {
List<String> names = Arrays.asList("张三", "里斯", "张五");
List<String> list = names.stream().filter(s -> s.startsWith("张")).collect(Collectors.toList());
for (String s : list) {
System.out.println("s = " + s);
}
}
}
filter 中传入的就是一个 Predicate 函数接口,这个接口接收 String 类型的数据,返回一个 boolean。
注意
一些常用类型的函数接口,JDK 中直接提供了相关的类供我们使用,例如 Predicate<Integer>
可以用 IntPredicate
代替;Consumer<Integer>
可以用 IntConsumer
代替
Consumer
消费数据,只有输入没有输出。
public class LambdaDemo04 {
public static void main(String[] args) {
List<String> names = Arrays.asList("张三", "里斯", "张五");
names.stream().forEach(s -> System.out.println(s));
}
}
Supplier
Supplier 刚好和 Consumer 相反,它只有输出没有输入。有的时候我们的工厂方法没有输入只有输出,这个时候就可以考虑使用 Supplier(如果有输入参数,则可以考虑使用 Function 函数接口)
Supplier<Connection> supplier = ()->{
Connection con = null;
try {
con = DriverManager.getConnection("", "", "");
} catch (SQLException e) {
e.printStackTrace();
}
return con;
};
Connection connection = supplier.get();
方法引用
将方法调用采用 :: 进行调用
静态方法引用
public class LambdaDemo05 {
public static void main(String[] args) {
Function<Integer, String> func = a -> String.valueOf(a);
String s = func.apply(99);
System.out.println("s = " + s);
}
}
在上面的这个 Function 中,我们将一个 Integer 类型数字转为了一个字符串,由于在 Lambda 中并没有其他代码,就是一个简单的类型转换,因为我们可以将之简写成如下方式
public class LambdaDemo05 {
public static void main(String[] args) {
Function<Integer, String> func = String::valueOf;
String s = func.apply(99);
System.out.println("s = " + s);
}
}
实例方法引用‘
Random random = new Random();
IntUnaryOperator func = i -> random.nextInt(i);
Integer r = func.applyAsInt(10);
System.out.println("r = " + r);
这段代码也可以使用方法引用,如下:
Random random = new Random();
IntUnaryOperator func = random::nextInt;
Integer r = func.applyAsInt(10);
System.out.println("r = " + r);
就是把类换成实例而已,其他都是一样的。
构造方法引用
对象构造
Supplier<Cat> supplier = () -> new Cat();
Cat cat = supplier.get();
可简写为:
Supplier<Cat> supplier = Cat::new;
Cat cat = supplier.get();
数组构造
IntFunction<int[]> func = (i) -> new int[i];
int[] arr = func.apply(10);
System.out.println("arr.length = " + arr.length);
可写为
IntFunction<int[]> func = int[]::new;
int[] arr = func.apply(10);
System.out.println("arr.length = " + arr.length);
变量引用
内部类中使用外部定义的变量,需要这个变量是一个 final 类型的,如果用了 Lambda 表达式,这个规则依然适用。
String s = "javaboy";
Consumer<String> consumer = s1 -> System.out.println(s1 + s);
consumer.accept("hello ");
此时虽然不用给 s 变量添加 final 标记,但是它实际上已经是 final 类型的了,如果强行修改,就会报错
类型推断
大部分情况下,Lambda 表达式都是可以推断出自己的类型的,个别情况下可能推断不出,比如出现方法重载的时候,这个时候可能就需要我们类型强转了
@FunctionalInterface
interface ICalculator2{
int add(int a, int b);
}
@FunctionalInterface
interface ICalculator3{
int multiply(int a, int b);
}
public class LambdaDemo06 {
public static void main(String[] args) {
calculator((ICalculator2) (a, b) -> a + b);
}
public static void calculator(ICalculator2 iCalculator) {
}
public static void calculator(ICalculator3 iCalculator) {
}
}
stream流简介
流的创建
集合
List 集合或者 Set 集合都可以直接搞一个流出来
List<String> list = new ArrayList<>();
Stream<String> s1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> s2 = set.stream();
数组
(1)通过数组获取流
IntStream stream = Arrays.stream(new int[]{11, 22, 33, 44, 55, 66});
(2)直接利用 IntStream、LongStream 等对象创建一个数字 Stream
IntStream s1 = IntStream.of(1, 2, 3);
DoubleStream s2 = DoubleStream.of(1, 2, 3);
LongStream s3 = LongStream.of(1L, 2L, 3L);
(3)自己创建流
Random random = new Random();
Supplier<Integer> supplier = () -> random.nextInt(100);
Stream<Integer> stream = Stream.generate(supplier).limit(5);
调用 Stream.generate 方法可以自己创建一个流,自己创建的时候需要提供一个 Supplier,通过调用 Supplier 中的 get 方法自动获取到元素。
中间操作
中间操作可以分为两大类:
-
map 或者 filter 会从输入流中获取每一个元素,并且在输出流中得到一个结果,这些操作没有内部状态,称为无状态操作。
-
reduce、sum、max 这些操作都需要内部状态来累计计算结果,所以称为有状态操作。
无状态操作
-
map/mapToXxx
-
flatMap/flatMapToXxx
-
filter
-
peek
有状态操作
-
distinct
-
sorted
-
limit/skip
map:对数据进行二次加工
Stream.map() 是 Stream 中最常用的一个转换方法,可以把一个 Stream 对象转为另外一个 Stream 对象。map 方法所接收的参数就是一个 Function 对象
例:一个字符串数组转为数字
String[] arr = {"1", "2", "3"};
Stream<String> s1 = Arrays.stream(arr);
Stream<Integer> s2 = s1.map(i -> Integer.valueOf(i));
JDK 中也提供了一些现成的格式转换,可以直接将元素转为 Double、Long、Obj 等类型
String[] arr = {"1", "2", "3"};
Stream<String> s1 = Arrays.stream(arr);
s1.mapToLong(i -> Long.parseLong(i)).forEach(System.out::println);
flatMap:多个流一起操作
flatMap 可以把 Stream 中的每个元素都映射为一个 Stream,然后再把这多个 Stream 合并为一个 Stream。
Stream<Integer[]> s = Stream.of(new Integer[]{1, 2, 3}, new Integer[]{4, 5, 6}, new Integer[]{7, 8, 9});
s.forEach(System.out::println);
通过 flatMap 我们可以将 Stream 中的元素变为 Integer
Stream<Integer> s = Stream.of(new Integer[]{1, 2, 3}, new Integer[]{4, 5, 6}, new Integer[]{7, 8, 9}).flatMap( i -> Arrays.stream(i));
s.forEach(System.out::println);
filter:过滤
filter 操作会对一个 Stream 中的所有元素一一进行判断,不满足条件的就被过滤掉了,剩下的满足条件的元素就构成了一个新的 Stream。
例:找到数组中所有大于 3 的元素
IntStream.of(2, 3, 4, 5, 6, 7).filter(i -> i > 3).forEach(System.out::println);
peek:对数据消费一次
peek 的入参是 Consumer,没有返回值,因此当我们要对元素内部进行处理时,使用 peek 是比较合适的,这个时候可以不用 map(map 的入参是 Function,它是有返回值的)。peek 方法本身会继续返回流,可以对数据继续进行处理。
IntStream.of(2, 3, 4, 5, 6, 7).filter(i -> i > 3).peek(String::valueOf).forEach(i-> System.out.println(i));
distinct:去重
这个是去重。由于去重操作需要获取到其他元素的值(比较之后才知道是否重复),所以这个是有状态操作。
IntStream.of(2, 3, 4, 3, 7, 6, 2, 5, 6, 7).distinct().forEach(System.out::println);
sorted:排序
sorted 是排序,因为也需要知道其他元素的值,然后才能去重,所以这个也是有状态操作
IntStream.of(2, 3, 4, 3, 7, 6, 2, 5, 6, 7).distinct().sorted().forEach(System.out::println);
limit/skip:配合操作,类似分页
limit 和 skip 配合操作有点像数据库中的分页,skip 表示跳过 n 个元素,limit 表示取出 n 个元素。
Arrays.asList('A', 'B', 'C', 'D', 'E', 'F').stream().skip(2).limit(3).forEach(System.out::println);
这个会跳过 A 和 B,最终打印出 C D E。
终止操作
终止操作就是最终计算出结果的操作,只要方法的返回值不是一个 Stream,那就是终止操作,否则就是中间操作。
终止操作又分为两类:
-
短路操作:不用处理全部元素就可以返回结果。
-
非短路操作:必须处理所有元素才能得到最终结果。
非短路操作
-
forEach/forEachOrdered
-
collect/toArray
-
reduce
-
min/max/count
短路操作
-
findFirst/findAny
-
allMatch/anyMatch/noneMatch
forEach/forEachOrdered
forEach 和 forEachOrdered 都是接收一个 Consumer 类型的参数,完成对参数的消费,不同的是,在并行流中,forEachOrdered 会保证执行顺序。
例:
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Arrays.stream(arr).parallel().forEach(System.out::println);
Arrays.stream(arr).parallel().forEachOrdered(System.out::println);
前者打印出来的顺序不一定是 123456789,后者一定是。
collect/toArray
这两个都是收集器,可以将执行结果转为一个 List 集合或者一个数组
List<Integer> list = Stream.of(1, 2, 3, 4).filter(p -> p > 2).collect(Collectors.toList());
System.out.println(list);
reduce
reduce 是 Stream 的一个聚合方法,它可以把一个 Stream 的所有元素按照聚合函数聚合成一个结果。reduce 方法传入的对象是BinaryOperator 接口,它定义了一个apply 方法,负责把上次累加的结果和本次的元素进行运算,并返回累加的结果。
例:数组求和,当然可以直接调用 sum 计算,可以调用 reduce 来实现
Optional<Integer> optional = Stream.of(1, 2, 3, 4).reduce((i, j) -> i + j);
System.out.println(optional.orElse(-1));
reduce 的参数是 BinaryOperator,这个接收两个参数,第一个参数是之前计算的结果,第二个参数是本次参与计算的元素,两者累加求和。
min/max/count
求最大值最小值,统计总个数
Stream<Integer> s = Stream.of(1, 2, 3, 4);
long count = s.count();
System.out.println("count = " + count);
Optional<Integer> min = s.min(Comparator.comparingInt(i -> i));
System.out.println("min.get() = " + min.get());
findFirst/findAny
这两个就是返回流中的第一个、任意一个元素,findAny 要在并行流中测试才有效果,举个栗子
for (int i = 0; i < 10; i++) {
Optional<Integer> first = Stream.of(1, 2, 3, 4).parallel().findFirst();
System.out.println("first.get() = " + first.get());
}
System.out.println("=============");
for (int i = 0; i < 10; i++) {
Optional<Integer> first = Stream.of(1, 2, 3, 4).parallel().findAny();
System.out.println("first.get() = " + first.get());
}
allMatch/anyMatch/noneMatch
allMatch、anyMatch、noneMatch 用来判断所有元素、任意元素或者没有元素满足给定的条件。这三个方法的参数都是一个 Predicate 接口函数。
boolean b = Stream.of(1, 2, 3, 4).allMatch(i -> i > 5);
System.out.println("b = " + b);
并行流
通常情况下,对 Stream 的元素进行处理是单线程的,即一个一个元素进行处理。有时候我们希望可以并行处理 Stream 元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。
把一个普通 Stream 转换为可以并行处理的 Stream 非常简单,只需要用 parallel 方法进行转换:
new Random().ints().limit(50).parallel().forEach(i->{
System.out.println(Thread.currentThread().getName() + "--->" + i);
});
收集器
收集器可以将计算结果重新整理收集到一个集合中,这个集合可以是一个 List/Set 获取其他,并且还可以在收集的过程中对数据进行处理
例:
例如我有一个 users 集合,里边保存了用户数据,用户有 username、age 以及 gender 三个属性,如下代码分别表示:
-
提取出用户对象中的 age 属性组成新的集合并返回。
-
提取出用户对象中的 username 属性组成新的集合并返回。
-
提取出用户对象中的 gender 属性组成新的集合并返回(这里是一个 Set 集合,所以会自动去重)。
List<Integer> ages = users.stream().map(User::getAge).collect(Collectors.toList());
System.out.println("ages = " + ages);
List<String> usernames = users.stream().map(User::getUsername).collect(Collectors.toList());
System.out.println("usernames = " + usernames);
Set<String> genders = users.stream().map(User::getGender).collect(Collectors.toSet());
System.out.println("genders = " + genders);
Collectors.toList()
最终返回的是 ArrayList,Collectors.toSet()
最终返回的是 HashSet。
如果我们想返回一个 Vector 或者 TreeSet,也是可以的,如下:
List<Integer> ages = users.stream().map(User::getAge).collect(Collectors.toList());
System.out.println("ages = " + ages);
List<String> usernames = users.stream().map(User::getUsername).collect(Collectors.toCollection(Vector::new));
System.out.println("usernames = " + usernames);
TreeSet<String> genders = users.stream().map(User::getGender).collect(Collectors.toCollection(TreeSet::new));
System.out.println("genders = " + genders);
也可以获取某一个字段的统计信息:
IntSummaryStatistics ageStatistics = users.stream().collect(Collectors.summarizingInt(User::getAge));
System.out.println("ageStatistics = " + ageStatistics);
还可以对数据进行分块,将男女不同性别统计出来
Map<Boolean, List<User>> map = users.stream().collect(Collectors.partitioningBy(u -> u.getGender().equals("男")));
System.out.println("map = " + map);
也可以按照性别对数据进行分组
Map<String, List<User>> map2 = users.stream().collect(Collectors.groupingBy(User::getGender));
System.out.println("map2 = " + map2);
分组后,Map 中的 key 就是性别;分块后,Map 中的 key 就是 true/false。
再比如统计男女的人数:
Map<String, Long> map2 = users.stream().collect(Collectors.groupingBy(User::getGender,Collectors.counting()));
System.out.println("map2 = " + map2);
webflux
在响应式编程中,会有一个数据发布者 Publisher 和数据订阅者 Subscriber,Subscriber 接收 Publisher 发布的数据并进行消费,在 Subscriber 和 Publisher 之间还存在一个 Processor,类似于一个过滤器,可以对数据进行中间处理。
Publisher:生产者
package org.reactivestreams;
public interface Publisher<T> {
void subscribe(Subscriber<? super T> s);
}
Subscriber:消费者
package org.reactivestreams;
public interface Subscriber<T> {
// 第一次连接触发
//这个是订阅成功的回调方法,用于初始化 Subscription,并且表明可以开始接收订阅数据了
public void onSubscribe(Subscription s);
// 后续连接触发
//接收下一项订阅数据的回调方法。
public void onNext(T t);
// 出现异常时触发
//在 Publisher 或 Subcriber 遇到不可恢复的错误时调用此方法,之后 Subscription 不会再调用 Subscriber 其他的方法
public void onError(Throwable t);
// 当接收完所有订阅数据,并且发布者已经关闭后会回调这个方法。
public void onComplete();
}
Subscription:为发布者和订阅者之间的订阅关系,用来控制消息的消费
public static interface Subscription {
//这个方法用来向数据发布者请求 n 个数据。
public void request(long n);
// 取消消息订阅,订阅者将不再接收数据。
public void cancel();
}
Processor
Processor 是一个空接口,不过它同时继承了 Publisher 和 Subscriber,所以它既能发布数据也能订阅数据,因此我们可以通过 Processor 来完成一些数据转换的功能,先接收数据进行处理,处理完成后再将数据发布出去,这个也有点类似于我们 JavaEE 中的过滤器。
public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> {
}
Flux和Mono
// 只有完成信号的空数据流
Flux.just();
Flux.empty();
Mono.empty();
Mono.justOrEmpty(Optional.empty());
// 只有错误信号的数据流
Flux.error(new Exception("some error"));
Mono.error(new Exception("some error"));
订阅:subscribe
方法中的lambda表达式作用在了每一个数据元素上。此外,Flux和Mono还提供了多个subscribe
方法的变体
// 订阅并触发数据流
subscribe();
// 订阅并指定对正常数据元素如何处理
subscribe(Consumer<? super T> consumer);
// 订阅并定义对正常数据元素和错误信号的处理
subscribe(Consumer<? super T> consumer,
Consumer<? super Throwable> errorConsumer);
// 订阅并定义对正常数据元素、错误信号和完成信号的处理
subscribe(Consumer<? super T> consumer,
Consumer<? super Throwable> errorConsumer,
Runnable completeConsumer);
// 订阅并定义对正常数据元素、错误信号和完成信号的处理,以及订阅发生时的处理逻辑
subscribe(Consumer<? super T> consumer,
Consumer<? super Throwable> errorConsumer,
Runnable completeConsumer,
Consumer<? super Subscription> subscriptionConsumer);
例:
(1)订阅flux
Flux.just(1, 2, 3, 4, 5, 6).subscribe(
System.out::println,
System.err::println,
() -> System.out.println("Completed!"));
// 输出
1
2
3
4
5
6
Completed!
(2) 有错误信号
Mono.error(new Exception("some error")).subscribe(
System.out::println,
System.err::println,
() -> System.out.println("Completed!")
);
// 输出
java.lang.Exception: some error
zip 一对一合并流:多个流一对一的合并起来
例:二合一合并流
public static <T1,T2> Flux<Tuple2<T1,T2>> zip(Publisher<? extends T1> source1,
Publisher<? extends T2> source2)
public static <T1, T2> Mono<Tuple2<T1, T2>> zip(Mono<? extends T1> p1, Mono<? extends T2> p2)
Flux
的zip
方法接受Flux或Mono为参数,Mono
的zip
方法只能接受Mono类型的参数。
用于编程方式自定义生成数据流的create和generate等及其变体方法;
用于“无副作用的peek”场景的doOnNext、doOnError、doOncomplete、doOnSubscribe、doOnCancel等及其变体方法;
用于数据流转换的when、and/or、merge、concat、collect、count、repeat等及其变体方法;
用于过滤/拣选的take、first、last、sample、skip、limitRequest等及其变体方法;
用于错误处理的timeout、onErrorReturn、onErrorResume、doFinally、retryWhen等及其变体方法;
用于分批的window、buffer、group等及其变体方法;
用于线程调度的publishOn和subscribeOn方法。
Flux
(1) just
可以指定序列中包含的全部元素。创建出来的 Flux 序列在发布这些元素之后会自动结束。
(2) fromArray(),fromIterable()和 fromStream()
可以从一个数组、Iterable 对象或 Stream 对象中创建 Flux 对象。
(3) empty()
创建一个不包含任何元素,只发布结束消息的序列,在响应式编程中,流的传递是基于元素的,empty表示没有任何元素,所以不会进行后续传递,需要用switchIfEmpty等处理
(4) error(Throwable error)
创建一个只包含错误消息的序列。
(5) never()
创建一个不包含任何消息通知的序列。
(6) range(int start, int count)
创建包含从 start 起始的 count 个数量的 Integer 对象的序列。
Flux.range(1, 10)
.timeout(Flux.never(), v -> Flux.never())
.subscribe(System.out::println);
(7)interval
指定时间间隔进行计数
Flux.interval(Duration.ofSeconds(1)).doOnNext(System.out::println).blockLast();
执行结果
0
1
2
3
4
...
每秒输出一个数字。
(8)generate() 不太懂@yw
generate()方法通过同步和逐一的方式来产生 Flux 序列。序列的产生是通过调用所提供的 SynchronousSink 对象的 next(),complete()和 error(Throwable)方法来完成的。逐一生成的含义是在具体的生成逻辑中,next()方法只能最多被调用一次。在有些情况下,序列的生成可能是有状态的,需要用到某些状态对象。此时可以使用 generate()方法的另外一种形式 generate(Callable stateSupplier, BiFunction<S,SynchronousSink,S> generator),其中 stateSupplier 用来提供初始的状态对象。在进行序列生成时,状态对象会作为 generator 使用的第一个参数传入,可以在对应的逻辑中对该状态对象进行修改以供下一次生成时使用。
在代码清单 2中,第一个序列的生成逻辑中通过 next()方法产生一个简单的值,然后通过 complete()方法来结束该序列。如果不调用 complete()方法,所产生的是一个无限序列。第二个序列的生成逻辑中的状态对象是一个 ArrayList 对象。实际产生的值是一个随机数。产生的随机数被添加到 ArrayList 中。当产生了 10 个数时,通过 complete()方法来结束序列
Flux.generate(sink -> {
sink.next("Hello");
sink.complete();
}).subscribe(System.out::println);
final Random random = new Random();
Flux.generate(ArrayList::new, (list, sink) -> {
int value = random.nextInt(100);
list.add(value);
sink.next(value);
if (list.size() == 10) {
sink.complete();
}
return list;
}).subscribe(System.out::println);
(9) create()方法
create()方法与 generate()方法的不同之处在于所使用的是 FluxSink 对象。FluxSink 支持同步和异步的消息产生,并且可以在一次调用中产生多个元素。下方代码,在一次调用中就产生了全部的 10 个元素。
Flux.create(sink -> {
for (int i = 0; i < 10; i++) {
sink.next(i);
}
sink.complete();
}).subscribe(System.out::println);
Mono
(1) just
创建对象
(2) empty
创建一个不包含任何元素,只发布结束消息的序列
(3) error()
抛出异常,使用示例:
Mono.defer(()->{
return Mono.error(new RuntimeException());
}).subscribe();
(4) never()
empty里面至少还有一个结束消息,而never则是真的啥都没有
(5) fromCallable()
Mono.fromCallable(() -> "9999").subscribe(System.out::println);
(6) fromCompletionStage()
Mono.fromCompletionStage(future).block();
(7) fromFuture()、fromRunnable()和 fromSupplier()
分别从 Callable、CompletionStage、CompletableFuture、Runnable 和 Supplier 中创建 Mono。
(8) delay(Duration duration)和 delayMillis(long duration)
创建一个 Mono 序列,在指定的延迟时间之后,产生数字 0 作为唯一值。
Mono.delay(Duration.ofSeconds(3)).doOnNext(System.out::println).block();
(9)justOrEmpty(Optional<? extends T> data)和 justOrEmpty(T data)
从一个 Optional 对象或可能为 null 的对象中创建 Mono。只有 Optional 对象中包含值或对象不为 null 时,Mono 序列才产生对应的元素。
(10)create()方法来使用 MonoSink 来创建 Mono
Mono.fromSupplier(() -> "Hello").subscribe(System.out::println);
Mono.justOrEmpty(Optional.of("Hello")).subscribe(System.out::println);
Mono.create(sink -> sink.success("Hello")).subscribe(System.out::println);
操作符
(1)buffer 和 bufferTimeout,bufferUntil 和 bufferWhile
这两个操作符的作用是把当前流中的元素收集到集合中,并把集合对象作为流中的新元素。在进行收集时可以指定不同的条件:所包含的元素的最大数量和收集的时间。方法 buffer()仅使用一个条件,而 bufferTimeout()可以同时指定两个条件。指定时间间隔时可以使用 Duration 对象或毫秒数,即使用 bufferMillis()或 bufferTimeoutMillis()两个方法。
除了元素数量和时间之外,还可以通过 bufferUntil 和 bufferWhile 操作符来进行收集。这两个操作符的参数是表示每个集合中的元素所要满足的条件的 Predicate 对象。bufferUntil 会一直收集直到 Predicate 返回为 true。使得 Predicate 返回 true 的那个元素可以选择添加到当前集合或下一个集合中;bufferWhile 则只有当 Predicate 返回 true 时才会收集。一旦值为 false,会立即开始下一次收集
需要注意的是,在第二个案例中,首先通过 toStream()方法把 Flux 序列转换成 Java 8 中的 Stream 对象,再通过 forEach()方法来进行输出。这是因为序列的生成是异步的,而转换成 Stream 对象可以保证主线程在序列生成完成之前不会退出,从而可以正确地输出序列中的所有元素。
Flux.range(1, 100).buffer(20).subscribe(System.out::println);
# 输出结果
[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, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
[41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]
[61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80]
[81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
Flux.interval(Duration.ofMillis(100)).buffer(Duration.ofMillis(200)).take(2).toStream().forEach(System.out::println);
# interval(Duration.ofMillis(100))每隔一百毫秒产生一个数
# buffer(Duration.ofMillis(200)) 每隔二百毫秒收集得数集合到数组中
# 输出结果
[0, 1]
[2, 3]
Flux.interval(Duration.ofMillis(100)).bufferTimeout(7,Duration.ofMillis(105)).take(2).toStream().forEach(System.out::println);
# bufferTimeout(7,Duration.ofMillis(105)) 每次集合收集,最大数量为7,最大时间为105毫秒
# 输出结果
[0, 1]
[2, 3]
Flux.range(1, 10).bufferUntil(i -> i % 3 == 0).subscribe(System.out::println);
# bufferUntil(i -> i % 3 == 0):当值%3等于0时,完成本次集合收集
# 输出结果
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]
Flux.range(1, 10).bufferWhile(i -> i % 2 == 0).subscribe(System.out::println);
# bufferWhile(i -> i % 2 == 0):若满足条件,则加入集合
# 输出结果
[2]
[4]
[6]
[8]
[10]
(2)window
window 操作符的作用类似于 buffer,所不同的是 window 操作符是把当前流中的元素收集到另外的 Flux 序列中,因此返回值类型是 Flux。
Flux.range(1, 100).window(20).subscribe(System.out::println);
# 输出结果 这是因为 window 操作符所产生的流中包含的是 UnicastProcessor 类的对象,而 UnicastProcessor 类的 toString 方法输出的就是 UnicastProcessor 字符。
UnicastProcessor
UnicastProcessor
UnicastProcessor
UnicastProcessor
UnicastProcessor
(3) zipWith
zipWith 操作符把当前流中的元素与另外一个流中的元素按照一对一的方式进行合并。在合并时可以不做任何处理,由此得到的是一个元素类型为 Tuple2 的流;也可以通过一个 BiFunction 函数对合并的元素进行处理,所得到的流的元素类型为该函数的返回值。
在代码清单 8 中,两个流中包含的元素分别是 a,b 和 c,d。第一个 zipWith 操作符没有使用合并函数,因此结果流中的元素类型为 Tuple2;第二个 zipWith 操作通过合并函数把元素类型变为 String。