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
中间操作
Stream的中间操作分为两大类:
- 无状态:map 或 filter 会从输入流中获取每一个元素, 然后在输出流中得到一个结果, 这些操作没有内部状态, 所以称为无状态操作。
- 有状态:reduce,sum,max 这些操作都需要内部状态来累计计算结果, 所以称为有状态操作
无状态操作
- 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<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那么就是终止操作
终止操作分两类:
-
短路操作:不用处理全部元素就可以返回结果的操作
-
非路操作:必须处理完所有的元素才能够得到最终结果的操作
非短路操作 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基础的主要内容,如果未能解决你的问题,请参考以下文章
Java语言编程经验之基础语法20-Lambda&方法引用-21-函数式接口&Stream流