Java8新特性: Stream流式操作

 

一、Stream流简介


 

Java 8 中的 Stream 流和 Java IO 中的各种流没有任何关系。

Java8 中的 Stream 不存储数据,它通过函数式编程模式来对集合进行链状流式操作。

Stream 的操作大体上分为两种:中间操作终止操作

  1. 中间操作:可以有多个,每次返回一个新的流(Stream),可进行链式操作。

  2. 终端操作:只能有一个,每次执行完,这个流也就处理结束了,无法执行下一个操作,因此只能放在最后。

举个例子:

int[] arr = {1, 2, 3, 4, 5, 6};
Arrays.stream(arr).filter(i -> i > 3).count();

 

意思就是返回数组 arr 中值大于 3 的元素数量。

其中,count() 就是一个终端操作filter() 就是一个中间操作filter() 还是返回的新的流。

IntStream intStream = Arrays.stream(arr).filter(i -> i > 3);

 

 

二、Stream的创建 


 

创建 Stream 流的方式有多种:数组、集合、数字 Stream、自己创建:

// 1.集合调用 stream() 方法获取流
List<String> list = new ArrayList<>();
list.add("1");
Stream<String> stream = list.stream();

// 2.数组 Arrays.stream() 获取流
IntStream stream = Arrays.stream(new int[]{1, 2, 3, 4, 5});

// 3.Stream.of() 方法获取流
IntStream intStream = IntStream.of(1, 2, 4);
DoubleStream doubleStream = DoubleStream.of(1, 2, 4);

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

 

 

三、Stream的操作


 

Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。

操作流的方法有很多,有中间操作终端操作

其中中间操作又可以分为两大类:无状态操作、有状态操作。

  • map 或者 filter 会从输入流中获取每一个元素,并且在输出流中得到一个结果,这些操作没有内部状态,称为无状态操作
  • reduce、sum、max 这些操作都需要内部状态来累计计算结果,所以称为状态操作

以上这些概念性的东西看看就好,还是挑一些常用的方法直接上代码看看 Stream 流的具体操作吧。

 

3.1 filter过滤器

filter 顾名思义,就是过滤、筛选的意思。它是一个中间操作,返回的是一个新的 Stream 。

filter 操作会对一个 Stream 中的所有元素一一进行判断,不满足条件的就被过滤掉了,剩下的满足条件的元素就构成了一个新的 Stream。

List<String> list = new ArrayList<>();
list.add("小黑");
list.add("小胖");
list.add("小六");
list.add("一鑫");
Stream<String> stream = list.stream().filter(item -> item.contains("小"));
stream.forEach(System.out::println);

这里我们使用 filter 操作过滤出了包含“小”字的名字。

结果如下:

小黑
小胖
小六

filter() 方法接收的是一个 Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数,关于 Predicate、Supplier、Consumer 等等这些 JDK 新增的函数式接口可以另写一篇文章来分析,这里就不讲述了。

 

3.2 map转换器

Stream.map()Stream 中最常用的一个转换方法,可以把一个 Stream 对象转为另外一个 Stream 对象。

List<String> list = new ArrayList<>();
list.add("小黑");
list.add("小胖");
list.add("小六");
list.add("一鑫");
Stream<String> stream = list.stream().map(item -> "二班" + item);
stream.forEach(System.out::println);

这里我们使用 map 操作给各项加上了前缀“二班”。

结果如下:

二班小黑
二班小胖
二班小六
二班一鑫

map() 方法接收的是一个 Function(Java 8 新增的一个函数式接口,接受一个输入参数 T,返回一个结果 R)类型的参数。

再举个例子,把字符串数组转为数字:

String[] arr = {"1", "2", "3"};
Stream<String> s1 = Arrays.stream(arr);
Stream<Integer> s2 = s1.map(i -> Integer.valueOf(i));
s2.forEach(System.out::println);

这其中的 i -> Integer.valueOf(i) 就是一个 Function ,输入参数是 String ,返回 Integer。就相当于这样:

Function<String, Integer> fun = i -> Integer.valueOf(i);
Stream<Integer> s2 = s1.map(fun);

 

3.3 match匹配器

有如下 3 个匹配的方法:

  • anyMatch(),只要有一个元素匹配传入的条件,就返回 true。

  • allMatch(),只有有一个元素不匹配传入的条件,就返回 false;如果全部匹配,则返回 true。

  • noneMatch(),只要有一个元素匹配传入的条件,就返回 false;如果全部都不匹配,则返回 true。

List<String> list = new ArrayList<>();
list.add("小黑");
list.add("小胖");
list.add("小六");
list.add("一鑫");

boolean anyMatchFlag = list.stream().anyMatch(item -> item.contains("小"));
boolean allMatchFlag = list.stream().allMatch(item -> item.length() > 1);
boolean noneMatchFlag = list.stream().noneMatch(item -> item.startsWith("小"));
System.out.println(anyMatchFlag);
System.out.println(allMatchFlag);
System.out.println(noneMatchFlag);

结果如下:

true
true
false

 

3.4 reduce组合器

reduce 是 Stream 的一个聚合方法,它可以把一个 Stream 的所有元素按照聚合函数聚合成一个结果。reduce 方法传入的对象是BinaryOperator 接口,它定义了一个 apply 方法,负责把上次累加的结果和本次的元素进行运算,并返回累加的结果。

举个例子,数组求和:

Optional<Integer> optional = Stream.of(1, 2, 3, 4, 5).reduce((a, b) -> a + b);
System.out.println(optional);
System.out.println(optional.orElse(-1));

结果如下:

Optional[15]
15

Optional<T> 对象是一种包装器对象,它在值不存在的情况下会产生一个可替代物,二只有在值存在的情况下才会使用这个值。

Optional可以用来解决臭名昭著的空指针异常(NullPointerException),这里的 orElse 方法意思是在对象为空的时候返回默认值 -1 。

Optional 在《Java核心技术卷2》中 Java 8 的流库这一章节中还占了一些篇幅,这里没法写了,写下来就是一篇新的文章了。

还有一种情况:

Integer reduce = Stream.of(1, 2, 3, 4, 5).reduce(6, (a, b) -> a + b);
System.out.println(reduce);

有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。

结果如下:

21

  
 

3.5 Collectors收集器

集合或数组既可以转成流,相应的我们也可以使用 collect、toArray 方法将流转回去。

List<String> list = new ArrayList<>();
list.add("小黑");
list.add("小胖");
list.add("小六");
list.add("一鑫");

String[] strArray = list.stream().toArray(String[]::new);
System.out.println(Arrays.toString(strArray));

List<String> list1 = list.stream().map(item -> "二班" + item).collect(Collectors.toList());
List<String> list2 = list.stream().collect(Collectors.toCollection(ArrayList::new));
System.out.println(list1);
System.out.println(list2);

String str = list.stream().collect(Collectors.joining("|"));
System.out.println(str);

结果如下:

[小黑, 小胖, 小六, 一鑫]
[二班小黑, 二班小胖, 二班小六, 二班一鑫]
[小黑, 小胖, 小六, 一鑫]
小黑|小胖|小六|一鑫

Collectors 是一个收集器的工具类,内置了一系列收集器实现,比如 :

  • toList() 方法将元素收集到一个新的 java.util.List 中;
  • toCollection() 方法将元素收集到一个新的 java.util.ArrayList 中;
  • joining() 方法将元素收集到一个可以用分隔符指定的字符串中。

收集到映射表中

假设我们有一个 Stream<Person>,并且想要将其元素收集到一个映射表中,这样后续就可以通过它们的 ID 来查找人员了。我们可以使用 Collectors.toMap 方法:

Stream<Person> stream = Stream.of(new Person(1, "小胖"), new Person(2, "小黑"), new Person(3, "小六"));
Map<Integer, String> map = stream.collect(Collectors.toMap(Person::getId, Person::getName));
System.out.println(map);

结果如下:

{1=小胖, 2=小黑, 3=小六}

这里值也可以是自己(person 对象),可以这样写:

Stream<Person> stream = Stream.of(new Person(1, "小胖"), new Person(2, "小黑"), new Person(3, "小六"));
Map<Integer, Person> map = stream.collect(Collectors.toMap(Person::getId, Function.identity(), (o1, o2) -> o1));
System.out.println(map);

结果如下:

{1=Person@2f4d3709, 2=Person@4e50df2e, 3=Person@1d81eb93} 

这里的 Function.identity() 返回一个输出跟输入一样的Lambda表达式对象。

如果多个元素具有相同的键,也会存在冲突,(o1, o2) -> o1 就是解决的办法,意思是发生冲突时,永远使用第一个覆盖后续的。

 

3.6 其他

上面列出了部分常用操作,其实还有很多没有列出来:

flatMap、peek、distinct、sorted、limit、skip、count、max、min、findFirst、findAny、forEach

也不在一个个的讲解了,直接上几个案例一看估计聪明的你就明白了。

System.out.println("======去重(distinct)======");
IntStream.of(2,3,4,5,6,2,4).distinct().forEach(System.out::println);

System.out.println("======去重+排序(orted)======");
IntStream.of(8,3,4,3,6,2,6).distinct().sorted().forEach(System.out::println);

System.out.println("======跳过+限制(skip + limit)======");
Arrays.asList("A", "B", "C", "D", "E", "F").stream().skip(2).limit(3).forEach(System.out::println);

System.out.println("======统计个数(count)======");
System.out.println(Stream.of(1, 2, 3, 4).count());

System.out.println("======统计最小值(min)======");
Stream<Integer> s = Stream.of(1, 2, 3, 4);
Optional<Integer> min = s.min(Comparator.comparingInt(i -> i));
System.out.println(min.get());

System.out.println("======查找第一个元素(findFirst)======");
Optional<Integer> first = Stream.of(1, 2, 3, 4).findFirst();
System.out.println(first.get());

 

 

 

更多资料参见:

  1.《Java核心技术卷1-2》

  2.https://juejin.cn/post/7039581261089751048

 


posted @ 2022-08-03 10:51  xuxh120  阅读(299)  评论(0编辑  收藏  举报