继上一篇文章 还不理解 Lambda 表达式?来一起学习下吧,今天我准备学习一下 Java 8 中的 Stream
流了。
为什么相关的优秀文章那么多,我却还要自己写一遍呐?写来写去东西都差不多。其实我只是想记录下来我所学习的东西,全当是笔记了。写下来肯定要记忆更深刻一点,并且方便以后复习。
当然,文章发布出来能够帮助到大家一点那就更好了。我只能尽量避免文章中出现错误。话不多说,开始今天的学习。
简介
Java 8 中的 Stream
流和 Java IO 中的各种流没有任何关系。
Java8 中的
Stream
不存储数据,它通过函数式编程模式来对集合进行链状流式操作。
Stream 的操作大体上分为两种:中间操作和终止操作
-
中间操作:可以有多个,每次返回一个新的流(Stream),可进行链式操作。
-
终端操作:只能有一个,每次执行完,这个流也就处理结束了,无法执行下一个操作,因此只能放在最后。
举个例子:
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、自己创建,直接看下面代码你就明白了。
// 集合调用 stream() 方法获取流
List<String> list = new ArrayList<>();
list.add("1");
Stream<String> stream = list.stream();
// 数组 Arrays.stream() 获取流
IntStream stream = Arrays.stream(new int[]{1, 2, 3, 4, 5});
// Stream.of() 方法获取流
IntStream intStream = IntStream.of(1, 2, 4);
DoubleStream doubleStream = DoubleStream.of(1, 2, 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
流的具体操作吧。
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 新增的函数式接口可以另写一篇文章来分析,这里就不讲述了。
map
Stream.map()
是 Stream
中最常用的一个转换方法,可以把一个 Stream 对象转为另外一个 Stream 对象。
/*
如果需要将流中的元素映射到另一个流中,可以使用map方法。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
Function种的抽象方法:
R apply(T t);
*/
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
,就相当于这样:
Function<String, Integer> fun = i -> Integer.valueOf(i);
Stream<Integer> s2 = s1.map(fun);
输入参数是 String ,返回 Integer。
函数式接口
可以参照《Java核心技术卷 I》第 245 页。
看这个例子
public static void mapStream() {
List<String> words = Arrays.asList("hello", "world", "I", "love", "you");
words.stream()
.map(String::length)
.forEach(System.out::println);
}
其中String::length
和 System.out::println
明显是 方法引用
(《Java核心技术卷 I》第 247 页),其中的方法引用等价于向方法传递参数的 lambda 表达式:
String::length 等价于 s -> s.length(),System.out::println 等价于 x -> System.out.println(x)
匹配
有如下 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
组合
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
类主要解决的问题是臭名昭著的空指针异常(NullPointerException),这里的 orElse
方法意思是在对象为空的时候返回默认值 -1 。
还有一种情况:
Integer reduce = Stream.of(1, 2, 3, 4, 5).reduce(6, (a, b) -> a + b);
System.out.println(reduce);
有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。
结果如下:
21
其他
上面列出了部分常用操作,其实还有很多没有列出来:
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());
结果如下:
转换 Stream
集合或数组既可以转成流,相应的我们也可以使用 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 的元素进行处理是单线程的,即一个一个元素进行处理。有时候我们希望可以并行处理 Stream 元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。
把一个普通 Stream 转换为可以并行处理的 Stream 非常简单,只需要使用 parallel
方法进行转换。
暂时写到这里,后续还有待完善。