Lambda 表达式延申-Stream基础

Posted Dreamsrj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lambda 表达式延申-Stream基础相关的知识,希望对你有一定的参考价值。

Stream

简介

JDK Stream和 Java IO中的流是两种东西, Stream是使用函数式编程模式来对集合数据进行链状流式操作。

基本玩法

对一个数组中的数字进行求和操作,Stream 代码如下:

int[] a = {1, 2, 5, 8, 3, 9};
int sum = IntStream.of(a).sum();

对一个数组中的数字进行取平均数操作,Stream 代码如下:

int[] a = {1, 2, 5, 8, 3, 9};
double avg = IntStream.of(a).average().getAsDouble();

对数据进行加工

对集合数据进行平方后求和:

double[] b = {1, 2, 5, 8, 3, 9};
double sum = DoubleStream.of(b).map(i -> Math.pow(i, 2)).sum();

操作的分类

上面代码涉及到两个概念:

  • 中间操作: 中途的求平方操作(没有终止操作时中间操作并不会实际执行,只有在终止操作执行时才会执行中间操作)
  • 终止操作:最终计算出需求的结果的操作。

只要方法返回的不是一个Stream对象, 那就是终止操作,否则就是中间操作。

Stream 的创建

集合

rrayList<Integer> list = new ArrayList<>();
list.stream();

HashSet<Integer> set = new HashSet<>();
set.stream();

LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
set2.stream();

因为java.util.Collection接口中定义了stream()方法, 所以所有的实现类都可以直接使用steram方法来创建Stream对象。

数组

Arrays.stream(b);

数组对象可以通过java.util.Arrays帮助中的stream()方法来创建Stream对象。

数字

可以直接通过IntStream, LongStream, DoubleStream 等对象来直接创建数字Stream对象:

IntStream s1 = IntStream.of(1, 2, 3);
DoubleStream s2 = DoubleStream.of(1, 2, 3);
LongStream s3 = LongStream.of(1L, 2L, 3L);

自己创建

假设我们需要生成一个有5个元素的随机数列表:

Random random = new Random();
Supplier<Integer> supplier = () -> random.nextInt(100);
Stream<Integer> limit = Stream.generate(supplier).limit(5);

Stream可以通过generate 方法来创建一个Stream对象, generate方法需要一个Supplier对象, Stream会使用supplier中的get方法来获取需要的数据。

上面() -> random.nextInt(100);就是提供了一个Supplier接口的实现, 在generate方法中会调用我们实现的逻辑。

需要注意的是,无论怎么样创建Stream对象, 它都不会存储数据而只是操作数据。

Stream Operation

graph TD A(Stream Operation) -->B1(中间操作) B1 -->D1(stateful 有状态) B1 -->D2(unstateful 无状态) A --> B2(终止操作) B2 -->C1(短路操作) B2 -->C2(非短路操作) D1 --> E1(map) D1 --> E2(flatMap) D1 --> E3(filter) D1 --> E4(peek) D2 --> E5(distinct) D2 --> E6(sorted) D2 --> E7(limit/skip) C2 --> F1(forEach/forEachOrdered) C2 --> F2(collect/toArray) C2 --> F3(reduce) C2 --> F4(max/min/count) C1 --> F5(findFirst/findAny) C1 --> F6(allMatch/anyMatch/noneMatch)

中间操作

Stream的中间操作分为两大类:

  • 无状态:map 或 filter 会从输入流中获取每一个元素, 然后在输出流中得到一个结果, 这些操作没有内部状态, 所以称为无状态操作
  • 有状态:reduce,sum,max 这些操作都需要内部状态来累计计算结果, 所以称为有状态操作
graph TD B1(中间操作) B1 -->D1(有状态) B1 -->D2(无状态) D1 --> E1(map) D1 --> E2(flatMap) D1 --> E3(filter) D1 --> E4(peek) D2 --> E5(distinct) D2 --> E6(sorted) D2 --> E7(limit/skip)

无状态操作

  • map/mapToXXX
  • flatMap/flatMapToXXX
  • filter
  • peek

map

map方法可以将一个Stream对象转化为另一个Stream对象, map方法定义如下:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

可以看到,map 方法需要一个Function对象, 而Function对象是有输入且有输出的, 所以map方法可以用来将一个数据进行处理然后返回另一个数据。如下代码可以将一个字符串数组转换为数字数组:

String[] a = "1,2,3,4".split(",");
Stream<Integer> integerStream = Stream.of(a).map(i -> Integer.valueOf(i));
integerStream.collect(Collectors.toList()).forEach(System.out::println);

flatMap

flatMap 方法定如下

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

flatMap方法需要一个Function方法作为参数, 而这个function对象的返回类型是一个Stream。所以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);

上面代码输出为:

[Ljava.lang.Integer;@3b192d32
[Ljava.lang.Integer;@16f65612
[Ljava.lang.Integer;@311d617d

使用flatMap处理后:

Stream<Integer[]>s = Stream.of(new Integer[]{1, 2, 3}, new Integer[]{4, 5, 6}, new Integer[]{7, 8, 9});
s.flatMap(i -> Stream.of(i)).forEach(System.out::print);

输出为:

123456789

filter

filter 方法定义如下:

Stream<T> filter(Predicate<? super T> predicate);

filter 方法需要一个 Predicate对象, 而Predicate对象的作用是根据输入参数返回一个boolean值,所以Predicate的作用是判断输入的参数是否符合特定的要求。

因此 filter 方法会对Stream中的数据进行判断,然后返回符合要求的数据。

如下代码,我们只需要数据大于13的数据:

@Test
public void filter() {
String[] a = "3,23,23,6,2,5,7,8,2,4,66,2,5".split(",");
Stream<Integer> integerStream = Stream.of(a).map(i -> Integer.valueOf(i));
integerStream.filter(i -> i > 13).forEach(System.out::println);
}

peek

方法定义

Stream<T> peek(Consumer<? super T> action);

peek 需要一个Consumer参数, Consumer对象是一个有输入没有输出的函数接口,所以该方法用于消费数据。

如果需要使用数据时可以使用peek数据。

peek是 中间操作, 如果没有终止操作并不是实际执行

peek 方法会返回一个有原有流元素组成的流

String[] a = "3,23,23,6,2,5,7,8,2,4,66,2,5".split(",");
Stream<Integer> integerStream = Stream.of(a).map(i -> Integer.valueOf(i));
integerStream.filter(i -> i > 13).peek((i) -> {
System.out.println(i+ "---");
}).forEach(i -> System.out.println(">"+ i));
23---
>23
23---
>23
66---
>66

有状态操作

  • distinct
  • sorted
  • limit、skip

distinct

该方法通过使用Object.equals()方法比较每一个数据,去除重复数据后返回没有重复数据的流

String[] a = "3,23,23,6,2,5,7,8,2,4,66,2,5".split(",");
Arrays.stream(a).map(i -> Integer.valueOf(i)).distinct().forEach(System.out::println);

因为去重需求直到其他元素,所以这是有状态的。

sorted

该方法通过自然排序对数据进行排序,然后返回排序后的流

String[] a = "3,23,23,6,2,5,7,8,2,4,66,2,5".split(",");
Arrays.stream(a).map(i -> Integer.valueOf(i)).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);

终止操作

终止操作就是会计算出结果的操作。 只要方法返回的不是一个Stream那么就是终止操作

终止操作分两类:

graph TD B2(终止操作) B2 -->C1(短路操作) B2 -->C2(非短路操作) C2 --> F1(forEach/forEachOrdered) C2 --> F2(collect/toArray) C2 --> F3(reduce) C2 --> F4(max/min/count) C1 --> F5(findFirst/findAny) C1 --> F6(allMatch/anyMatch/noneMatch)
  • 短路操作:不用处理全部元素就可以返回结果的操作

  • 非路操作:必须处理完所有的元素才能够得到最终结果的操作

非短路操作 non-interfering

  • forEach/forEachOrdered
  • collect/toArray
  • reduce
  • min/max/count

forEach / forEachOrdered

这两个操作都需要一个Consumer参数,完成对参数的消费。 但是forEachOrdered在并行流中会保证其顺序

int[] arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i;
}

Arrays.stream(arr).parallel().forEach(i -> System.out.print(i + ", ")); //1
System.out.println();
Arrays.stream(arr).parallel().forEachOrdered(i -> System.out.print(i + ", ")); //2

代码1的输出并不保证其顺序与数组中元素顺序一致, 但是代码2保证其顺序一致。

collect/toArray

这两个操作都是收集器, 可以将执行结果收集到一个集合或数组中。

int[] arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
int[] ints = Arrays.stream(arr).parallel().filter(i -> i > 5).toArray();


List<Integer> list = Stream.of(1, 2, 3, 4).filter(p -> p > 2).collect(Collectors.toList());

reduce

这是一个聚合函数, 可以将stream中的元素聚合为一个结果。 方法定义如下:

Optional<T> reduce(BinaryOperator<T> accumulator);

BinaryOperator 中有一个apply方法, 接收两个参数。该方法会将接收的参数然后返回一个结果, 所以可以用于处理数据的聚合。

reduce会将返回的结果做为第一个参数,所以reduce可以做累加这类操作。

Stream.of(1, 23, 4, 5).reduce((i, j) -> {
System.out.print(i + " + ");
System.out.print(j + " = ");
System.out.print((i + j));
System.out.println();
return i + j;
});

1 + 23 = 24
24 + 4 = 28
28 + 5 = 33


max/min/count

这三个方法就是对集合中的数据求最大,最小,个数操作。

Integer min = Stream.of(1, 23, 4, 5).min(Comparator.comparingInt(i -> i)).get();
Integer max = Stream.of(1, 23, 4, 5).max(Comparator.comparingInt(i -> i)).get();
long count = Stream.of(1, 23, 4, 5).count();

短路操作

  • findFirst/findAny
  • allMatch/anyMatch/noneMatch

find

这两个就是返回流中的第一个、任意一个元素,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());
}

match

allMatch、anyMatch、noneMatch 用来判断所有元素、任意元素或者没有元素满足给定的条件。这三个方法的参数都是一个 Predicate 接口函数。

boolean b = Stream.of(1, 2, 3, 4).allMatch(i -> i > 5);
System.out.println("b = " + b);

并行流

普通的Stream是一个元素接一个元素进行处理数据, 当数据量较大时,并行处理数据可以加快数据处理速度。

使用parallel就可以将普通流转换为并行流

new Random().ints().limit(50).parallel().forEach(i->{
System.out.println(Thread.currentThread().getName() + "--->" + i);
});

收集器

收集一个集合中每一个元素的某一个属性为新集合

toList/toSet

List<Integer> ages = users.stream().map(User::getAge).collect(Collectors.toList());

统计集合中每一个对象的某一个属性

user.stream().collect(Collectors.summarizingInt(User::getAge));

统计可以是:求和,平均值,最大、最小值

对数据根据某一个条件分块

Map<Boolean, List<User>> map = users.stream().collect(Collectors.partitioningBy(u -> u.getGender().equals("男")));

对数据根据某一个条件分组

Map<String, List<User>> map2 = users.stream().collect(Collectors.groupingBy(User::getGender));

分组后,Map 中的 key 就是性别;分块后,Map 中的 key 就是 true/false。

分组后再统计

Map<String, Long> map2 = users.stream().collect(Collectors.groupingBy(User::getGender,Collectors.counting()));

以上是关于Lambda 表达式延申-Stream基础的主要内容,如果未能解决你的问题,请参考以下文章

lambda表达式与Stream流(非io)

Java8特性详解 lambda表达式 Stream

java基础复习

Java语言编程经验之基础语法20-Lambda&方法引用-21-函数式接口&Stream流

Java没基础函数式编程——Stream API 中的收集器

Java8 新特性:Lambda 表达式方法和构造器引用Stream API新时间与日期API注解