Stream流的常用方法
JDK 8为聚合操作中的Stream流对象提供了非常丰富的操作方法,这些方法被划分为中间操作和终结操作两种类型。这两种类型操作方法的根本区别就是方法的返回值,只要返回值类型不是Stream类型的就是终结操作,将会终结当前流模型,而其他的操作都属于中间操作。
接下来,通过一张表来展示Stream流对象的常用方法,如表1所示。
表1 Stream流的常用方法
方法声明 | 功能描述 |
---|---|
Stream<T> filter(Predicate<? super T> predicate) | 将指定流对象中的元素进行过滤,并返回一个子流对象 |
Stream<R> map(Function<? super T, ? extends R> mapper) | 将流中的元素按规则映射到另一个流中 |
Stream<T> distinct() | 删除流中重复的元素 |
Stream<T> sorted() | 将流中的元素按自然顺序排序 |
Stream<T> limit(long maxSize) | 截取流中元素的长度 |
Stream<T> skip(long n) | 丢弃流中前n个元素 |
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) | 将两个流对象合并为一个流 |
long count() | 统计流中元素的个数 |
R collect(Collector<? super T, A, R> collector) | 将流中的元素收集到一个容器中(如集合) |
Object[] toArray() | 将流中的元素收集到一个数组中 |
void forEach(Consumer<? super T> action) | 将流中的元素进行遍历 |
表6-8中,只是列出了Stream流对象的常用方法,其中一些方法还有多个重载方法。下面将选取表1中的常用方法进行演示说明。
1.遍历
遍历forEach()方法名称虽然与for循环中的“foreach()”相同,但是该方法是JDK 8新增的Stream接口中用于遍历流元素,并且该方法不保证元素的遍历过程在流中是被有序执行的。其方法声明如下:
void forEach(Consumer<? super T> action);
上述方法接收一个Consumer函数式接口参数(可以是一个Lambda或方法引用)作为遍历动作。接下来,通过一个案例对forEach()方法进行详细讲解,如文件1所示。
文件1 Example33.java
1 import java.util.stream.Stream;
2 public class Example33 {
3 public static void main(String[] args) {
4 // 通过字符串源数据创建了一个Stream流对象
5 Stream<String> stream = Stream.of("张三","李四","张小明","张阳");
6 // 通过forEach方法遍历Stream流对象中的元素
7 stream.forEach(i -> System.out.println(i));
8 }
9 }
运行结果如图1所示。
图1 运行结果
文件1中,先通过一组字符串源数据创建了一个Stream流对象,然后直接使用终结操作forEach()方法遍历了Stream流对象中的元素。其中第7行代码也可以使用第4章中讲解的方法引用来打印遍历出的流元素,第7行代码的修改方式如下:
stream.forEach(System.out::println);
2.过滤
使用过滤filter()方法可以将一个Stream流中的元素进行筛选转换成另一个子集流。该方法声明如下:
Stream<T> filter(Predicate<? super T> predicate);
上述方法接收一个Predicate函数式接口作为参数作为筛选条件。接下来,通过一个案例对filter()方法进行详细讲解,如文件2所示。
文件2 Example34.java
1 import java.util.stream.Stream;
2 public class Example34 {
3 public static void main(String[] args) {
4 // 通过字符串源数据创建了一个Stream流对象
5 Stream<String> stream = Stream.of("张三","李四","张小明","张阳");
6 stream.filter(i -> i.startsWith("张"))//筛选以“张”开头的元素
7 .filter(i -> i.length()>2) //筛选长度大于2个元素
8 .forEach(System.out::println); // 对流元素进行遍历输出
9 }
10 }
运行结果如图2所示。
图2 运行结果
文件2中,先通过一组字符串源数据创建了一个Stream流对象,然后以链式表达式的方式分别调用了filter()方法和forEach()方法对Stream流对象进行过滤和遍历操作。在过滤操作中,执行了两次filter()方法分别对字符串中以“张”开头和字符串长度大于2的元素进行了筛选,其中第6~7行代码中两个filter()方法中的两个筛选条件可以在一个filter()方法中使用逻辑运算符“&&”进行过滤操作,具体可以修改为如下方式:
stream.filter(i -> i.startsWith("张") && i.length() >2)
3.映射
Stream流对象的map()方法可以将流对象中的元素通过特定的规则进行修改然后映射为另一个流对象。该方法声明如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
上述方法接收一个Function函数式接口参数作为映射条件。接下来,通过一个案例对map()方法进行详细讲解,如文件35所示。
文件3 Example35.java
1 import java.util.stream.Stream;
2 public class Example35 {
3 public static void main(String[] args) {
4 // 通过字符串源数据创建了一个Stream流对象
5 Stream<String> stream = Stream.of("a1", "a2", "b1", "c2", "c1");
6 stream.filter(s -> s.startsWith("c")) // 筛选出流中以“c”开头的元素
7 .map(String::toUpperCase) // 对流元素进行映射,将全部字符改为大写
8 .sorted() // 对流元素进行排序
9 .forEach(System.out::println); // 对流元素进行遍历输出
10 }
11 }
运行结果如图3所示。
图3 运行结果
文件3中,先通过一组字符串源数据创建了一个Stream流对象,然后以链式表达式的方式分别调用了filter()方法、map()方法、sorted()方法和forEach()方法对Stream流对象进行聚合操作。在文件3中,map(String::toUpperCase)用于流对象映射,通过传入方法引入形式的参数“String::toUpperCase”将流中所有元素字母转为大写,而sorted()没有参数,用于对流元素进行自然排序。
4.截取
Stream流对象的limit()方法用于对流对象中的元素进行截取操作,该方法只需要一个参数,并且截取的是流中的前n个元素。在多数情况下,limit()方法会与skip()方法(跳过方法)组合使用,用于截取流对象中指定位置的多个元素。接下来,通过一个案例对limit()方法进行详细讲解,如文件4所示。
文件4 Example36.java
1 import java.util.stream.Stream;
2 public class Example36 {
3 public static void main(String[] args) {
4 // 通过字符串源数据创建了一个Stream流对象
5 Stream<String> stream = Stream.of("张三","李四","张小明","张阳");
6 stream.skip(1) // 跳过流中的前1个元素
7 .limit(2) // 截取流中的前2个元素
8 .forEach(System.out::println); // 对流元素进行遍历输出
9 }
10 }
运行结果如图4所示。
图4 运行结果
文件4中,先通过一组字符串源数据创建了一个Stream流对象,然后以链式表达式的方式分别调用了skip()、limit()和forEach()方法对流对象数据进行聚合操作。在原始流Stream对象中有4个元素,先通过skip(1)方法跳过了第1个元素,接着在剩余元素中使用limit(2)方法截取了前2个元素,最后使用forEach()方法遍历打印出截取的两个元素“李四”和“张小明”。
5.收集
在前面几个案例中,对Stream流元素进行中间操作后都是使用forEach()终结操作方法将元素遍历输出,显示的查看聚合操作后元素信息。像forEach()这种终结操作,在某些场景下并不可行,因为它无法将进行中间操作后的流元素作为我们熟悉的对象或者数据类型进行保存,为此,JDK 8中还为操作流对象增加了一个重要的终结操作——collect。
collect(收集)是一种是十分有用的终结操作,它可以把Stream中的元素保存为另外一种形式,比如集合、字符串等。该方法声明如下:
<R, A> R collect(Collector<? super T, A, R> collector);
collect()方法使用Collector作为参数,Collector包含四种不同的操作:supplier(初始构造器)、accumulator(累加器)、combiner(组合器)、finisher(终结者)。这些操作听起来很复杂,但是有一个好消息是JDK 8通过java.util.stream包下的Collectors类内置了各种复杂的收集操作,因此对于大部分操作来说,不需要开发者自己去实现Collectors类中的操作方法。接下来,通过一个案例对collect()方法进行详细讲解,如文件5所示。
文件5 Example37.java
1 import java.util.List;
2 import java.util.stream.Collectors;
3 import java.util.stream.Stream;
4 public class Example37 {
5 public static void main(String[] args) {
6 // 通过字符串源数据创建了一个Stream流对象
7 Stream<String> stream = Stream.of("张三","李四","张小明","张阳");
8 // 通过filter()方法筛选出字符串中以“张”开头的元素,
9 // 最后通过collect()方法进行终结操作,将流元素收集到一个List集合中
10 List<String> list = stream.filter(i -> i.startsWith("张"))
11 .collect(Collectors.toList());
12 System.out.println(list);
13 Stream<String> stream2 = Stream.of("张三","李四","张小明","张阳");
14 // 通过filter()方法筛选出字符串中以“张”开头的元素,
15 //通过collect()方法进行终结操作,将流元素使用" and "连接收集到一个字符串中
16 String string = stream2.filter(i -> i.startsWith("张"))
17 .collect(Collectors.joining(" and "));
18 System.out.println(string);
19 }
20 }
运行结果如图5所示。
图5 运行结果
文件5中,通过同一组字符串源数据创建了两个Stream流对象,然后以链式表达式的方式分别调用了filter()方法和collect()方法对流对象数据进行聚合操作。在collect()方法中,分别通过Collectors.toList()方法和Collectors.joining(" and ")方法将过滤后的流对象中的元素收集了到一个List集合和一个字符串中。
文件5只是演示了通过Stream接口的collect()方法将流对象元素收集为集合、字符串的用法,而Stream接口的另一个toArray()方法则支持将流对象元素收集为数组,这里就不再进行演示了。
注意:
一个Stream流对象可以连续进行多次中间操作,仍会返回一个流对象,但一个流对象只能进行一次终结操作,并且一旦进行终结操作后,该流对象就不复存在了。