Stream(流)是在Java 8中新增的新特性,首先需要为Java 8 Stream正名:Java 8中的Stream跟Java I/O Stream(例如:InputStream,OutputStream等)没有任何的关系。Stream是Java中资料来源的包装器,通过Stream我们可以快速的对资料来源进行操作(例如:过滤,排序、求和等等),且Stream不对任何资料进行储存,所以Stream也不是资料结构。
在Java 8中,Stream增强了Array,List等物件操作资料的能力,Stream提供了一些列的方法使的这些原有的集合类可以轻松建立Stream物件例项。通过Stream API提供的静态方法,可以快速的生成有限/无限的资料流。特别指出,Stream只是增强了原有集合类的能力,并未对底层的源代码做任何的修改。
1.Stream的工作流程
接下来,将介绍使用Java Stream的基本步骤。在Java 8中,Stream的生命周期一共有三个阶段:
1.获取资料来源并建立Stream例项。资料来源可以是阵列、列表、物件或者I/O流2.执行中间操作(Intermediate Operations)。中间操作可以是过滤、排序、型别转换等操作3.执行终端操作(Terminal Operation)。终端操作主要是对最终结果进行计数、求和、建立新集合等操作图1-1展示了Java 8 Stream的工作工作流程。
图 1-1 Stream工作流程
2.Stream 操作管道是什么?
所谓的Stream操作管道是指“中间操作”和“终端操作”的组合。在Java 8 Stream API中,许多的操作方法都将当前的Stream作为最终结果返回,这就允许开发人员以连式程式设计的方式,组合出更大的Stream操作管道。Java 8 Stream API位于java.util.stream包下,其组织结构如图2-1所示:
图2-1 java.util.stream包
接下来,将详细介绍“中间操作”和“终端操作”各自的方法和特性。
3.Stream中间操作(Intermediate Operations)是什么?
Stream的中间操作将返回一个Stream,它允许开发者以查询的方式呼叫多个其他的操作。特别注意的是,在“终端操作”方法未被呼叫之前,“中间操作”的方法不会被执行。Stream的“中间操作”一共包括七个:Stream.filter,Stream.map,Stream.flatmap,Stream.peek,Stream.sorted和Stream.limit。
3.1.Stream.filter
Stream.filter将返回一个包含与之谓词相匹配的元素的新的Stream。下面将通过一个示例,演示Stream.filter的使用方法。
执行结果:
此示例使用“中间操作”Stream.filter过滤以“j”开头的字串,并使用“终端操作”Stream.count统计以“j”开头的字串数量。
3.2.Stream.map
Stream.map将使用java.util.function.Function提供的方法转换Stream中的元素。Stream.map操作通常用于转换集合物件。下面是使用Stream.map的示例程式码:
执行结果:
3.3.Stream.flatmap
Stream.flatmap通常将Stream中的每一个元素转换成0个或多个元素。下面将通过flatmap来统计一个文字中单词的出现次数(不重复)。示例程式码如下:
执行结果:
source.txt内容:
java 8 stream example by ramostear
3.4.Stream.peek
Stream.peek在除错期间非常有用,它允许您在操作Stream之前检视Stream内部的资料。下面是示例程式码:
执行结果:
Java
C#
C++
GO
result size = 4
3.5.Stream.distinct
Stream.distinct是根据其内部的equals方法在Stream中对元素进行去重操作。下面是使用示例:
执行结果:
6
1
8
11
3.6.Stream.sorted
Stream.sorted方法用于将Stream中的资料元素进行排序。下面是使用示例:
执行结果:
1
6
8
11
3.7.Stream.limit
Stream.limit方法用于限定Stream中元素的个数。下面是使用示例:
执行结果:
0
1
2
3
4
5
11
618
4.Stream 终端操作(Terminal Operations)是什么?
终端操作用于生产最终的资料结果,如物件,阵列或者列表。在终端操作方法被执行前,中间操作方法将不会被执行。终端操作方法一共有十二个,它们是:forEach,toArray,reduce,collect,min,max,count,anymatch,allMatch,noneMatch,findFirst和findAny。表4-1列举了这十二个终端操作的使用方法:
图 4-1 Stream终端操作方法
5.如何建立Stream例项?
Stream支援多种资料来源建立Stream例项,如Array,List,Object和I/O流。接下来,通过简单的示例来演示如何建立Stream。
5.1 使用阵列建立Stream
首先,我们定义一个静态的阵列users,并初始几条使用者资料,程式码如下:
private static User[] users = {new User("user1"),new User("user2"),new User("user3")};
接下来,将利用users来建立一个Stream物件例项,示例程式码如下:
public static void fromArray() {
Stream uStream = Stream.of(users);
System.out.println("create stream from array.");
uStream.forEach(u->System.out.println(u.getUsername()));
}
5.2 使用物件建立Stream
沿用上面定义的users阵列,通过阵列下标获得使用者物件,并利用这些物件建立一个Stream例项,程式码如下:
public static void fromObjects() {
Stream uStream = Stream.of(users[0],users[1],users[2]);
System.out.println("create stream from objects.");
uStream.forEach(u->System.out.println(u.getUsername()));
}
5.3 使用List建立Stream
接下来,我们将users阵列转换成List,并使用此List来建立Stream例项。程式码如下:
public static void fromList() {
List list = Arrays.asList(users);
Stream uStream = list.stream();
System.out.println("create stream from list.");
uStream.forEach(u->System.out.println(u.getUsername()));
}
5.4 使用builder方法建立Stream
最后,我们将演示使用Stream内建的builder()静态方法建立Stream例项。程式码如下:
public static void byBuilder() {
Stream.Builder userStream = Stream.builder();
userStream.accept(users[0]);
userStream.accept(users[1]);
userStream.accept(users[2]);
System.out.println("create stream by builder.");
userStream.build().forEach(u->System.out.println(u.getUsername()));
}
以上就是通过资料来源建立Stream例项的几种不同方式。
6.为什么要使用Stream?
在指令式程式设计中,我们必须逐行编写程式码,才能完成相关的计算。例如,计算1~10000的数的总和,我们需要使用for(int i=1;i
接下来,表6-1展示了指令式程式设计和宣告式程式设计的优劣:
总结
在本文中,介绍了Java Stream的基本工作流程,并详细列举了中间操作和终端操作的细节。通过使用Stream,我们可以将传统的指令式程式设计程式码进行简化,以提高程式设计效率。