Java8新特性: Stream流式操作
一、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、自己创建:
// 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》