Java Stream API示例

时间:2020-01-09 10:35:15  来源:igfitidea点击:

Java中的Lambda表达式将功能性编程带给Java的方式,Java 8中的另一个添加功能,Java中的Stream API带来了处理对象集合的功能性方法。使用Java Stream API,我们可以在对象上创建流,然后只需要指定需要完成的操作,而不是必须完成的操作。
例如,如果要计算流中的元素,则需要指定获取流的源和计算元素的函数。 Stream API负责以优化的方式执行流管道。

List<Integer> myList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  
long count = myList.stream().count();
System.out.println("Count of elements in the list- " + count);

在上面的示例中,List myList是流的数据源,而count是对流执行的流操作。

在本Java Stream API教程中,我们将了解如何创建流,流的类型以及各种流操作示例。由于示例使用了lambda表达式和函数接口,因此请确保我们熟悉这些主题。

Java Stream API –接口和类

Java Stream API包含几个接口和类,这些接口和类打包在java.util.stream包中。层次结构的顶部是BaseStream接口,它为所有Streams提供基本功能。
BaseStream接口由interfacesDoubleStream,IntStream,LongStream和Stream扩展。
流接口是用于所有引用类型的通用接口。
DoubleStream,IntStream和LongStream是可以存储原始值的Stream的原始专业化。

在这篇文章中阅读有关原始流的更多信息Java中的原始类型流

Java Stream API中一个重要的类是Collectors类,它是Collector接口的实现,该类实现了各种有用的归约操作。

在此postCollectors类和Java中的collect()方法中了解有关Collector类的更多信息

创建流

在Java Stream API中,有多种获取流的方法。

1.集合中的流可以通过stream()和parallelStream()方法从任何类型的Collection中创建Stream。

List<Integer> myList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  
Stream<Integer> myStream = myList.stream();

2.来自数组的流可以通过Arrays.stream(Object []);从数组获得流。

String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);

3.使用Stream.of()方法还可以使用静态工厂方法Stream.of(Object [])创建Stream。

Stream<String> stream = Stream.of("a", "b", "c");

4.使用范围和迭代方法使用范围方法可以获取原始流。

IntStream stream = IntStream.range(1, 10);

通过使用迭代方法,可以实现相同的操作(获取整数1-9的流)

Stream<Integer> stream = Stream.iterate(1, n-> n < 10, n->n+1);

5.创建空流我们可以使用空方法创建空流。

Stream<Integer> stream = Stream.empty();

6.要获取文件行作为流,可以使用BufferedReader.lines()方法。

Path path = Paths.get("D:\theitroad\test.txt");
Stream<String> lines = Files.newBufferedReader(path).lines();

流操作的类型

流操作分为两种类型

  • 中间业务

  • 终端操作

Java Streams中的中间操作

中间操作返回一个新的流。此新流是对源流应用中间操作的结果。中间操作始终是惰性的,并且仅在执行终端操作时才执行这些操作。
例如,执行诸如filter()之类的中间操作实际上并不执行任何过滤,而是创建一个新的流,该新流在遍历时将包含与给定谓词匹配的初始流的元素。在执行管道的终端操作之前,不会开始遍历管道源。

中间业务又分为两类:

  • 无状态操作当处理新元素时,无状态操作(例如过滤器和映射)不会保留先前看到的元素的状态,每个元素都可以独立于其他元素上的操作进行处理。

  • 有状态操作有状态操作(例如不同的和已排序的)在处理新元素时可能会合并先前看到的元素的状态。

Java Stream API中一些中间操作的示例是filter,map,flatMap,distinct,sorted,limit。

中间流操作示例

  1. filter()在筛选器方法中,谓词作为参数传递,并且方法返回一个流,该流由与给定谓词匹配的该流的元素组成。谓词是一个功能接口,可以实现为lambda表达式。

我们想从ArrayList中过滤少于5个元素。

List<Integer> myList = Arrays.asList(11, 1, 9, 4, 98, 0, 17, 8, 2, 3);  
Stream<Integer> myStream = myList.stream().filter(n -> n > 5);
myStream.forEach(System.out::println);

输出:

11
9
98
17
8
  1. map()使用map方法,我们可以将流中的每个元素映射(转换)到另一个对象。方法返回一个流,该流包括将给定功能应用于此流的元素的结果。

如果我们有一个字符串列表,并且想要对每个字符串应用大写函数。

List<String> myList = Arrays.asList("rose", "lotus", "lily", "orchid");  
myList.stream().map(s -> s.toUpperCase()).forEach(System.out::println);

输出:

ROSE
LOTUS
LILY
ORCHID
  1. flatMap()flatMap()操作对流的元素进行一对多转换,还将生成的结构展平为新的流。如果对象包含许多嵌套对象,则flatMap()操作通过展平结构将所有嵌套级别的对象都置于同一级别。

如果要生成包含在数组的字符串中的单词流。

Stream<String> lineStream = Arrays.stream(lines);
Stream<String> str = lineStream.flatMap(line -> Stream.of(line.split(" +")));
  1. unique()返回由该流的不同元素组成的流。

Java Stream API的distinct()方法提供了从列表中删除重复项的最佳选择。

List<Integer> ListWithDuplicates = Arrays.asList(7, 8, 9, 7, 4, 2, 4, 1);
List<Integer> uniqueElementList = ListWithDuplicates.stream().distinct().collect(Collectors.toList());
System.out.println("List after removing duplicates : " + uniqueElementList);

输出:

List after removing duplicates : [7, 8, 9, 4, 2, 1]
  1. sorted()返回由该流的元素组成的流,并根据自然顺序进行排序。这是有状态的中间操作。
List<Integer> myList = Arrays.asList(7, 8, 9, 7, 4, 2, 4, 1);
myList.stream().sorted().forEach(System.out::println);

输出:

1
2
4
4
7
7
8
9
  1. limit()使用limit()方法,可以将Stream中的元素数限制为使用limit()方法传递的大小。
List<Integer> myList = Arrays.asList(7, 8, 9, 7, 4, 2, 4, 1);
myList.stream().limit(3).forEach(System.out::println);

输出:

7
8
9

Java Streams中的终端操作

一旦在Stream中执行了终端操作,就将流管道视为已消耗。不再可以使用相同的流;如果需要再次遍历同一数据源,则必须返回到数据源以获取新的流。
终端操作非常渴望(iterator()和spliterator()方法除外),在返回之前完成它们对数据源的遍历和对管道的处理。

Java Stream API中一些终端操作的示例是forEach,reduce,collect,min,max,count,findFirst。

终端流操作示例

  1. forEach()在forEach方法中,将Consumer作为参数传递,并且此Consumer操作在此流的每个元素上执行。
List<String> myList = Arrays.asList("rose", "lotus", "lily", "orchid");
myList.stream().forEach(System.out::println);

输出:

rose
lotus
lily
orchid
  1. reduce()reduce操作将流减少到单个非流值。此方法返回一个Optional描述减少的结果。

我们可以使用reduce方法在Collection中找到max元素。

List<Integer> myList = Arrays.asList(11, 1, 9, 4, 98, 0, 17, 8, 2, 3);
Optional<Integer> max = myList.stream().reduce((n1, n2) -> n1 > n2 ? n1:n2);
if(max.isPresent()){
	System.out.println("Max element in the List " + max.get());
}

输出:

Max element in the List 98
  1. collect()Java Stream API的collect()方法用于在可变容器(如List,StringBuilder)中收集Stream元素。

在示例列表中,过滤了所有小于5的元素,然后将流元素收集在TreeSet中,TreeSet对所得元素进行排序。

List<Integer> myList = Arrays.asList(11, 1, 9, 4, 98, 0, 17, 8, 2, 3);
Set<Integer> sortedSet = myList.stream().filter(n->n>5).collect(Collectors.toCollection((TreeSet::new)));
System.out.println("Sorted set- " + sortedSet);

输出:

Sorted set- [8, 9, 11, 17, 98]
  1. min()根据提供的Comparator返回此流的最小元素。此方法返回一个Optional,描述此流的最小元素。
List<Integer> myList = Arrays.asList(11, 1, 9, 4, 98, 0, 17, 8, 2, 3);
Optional<Integer> min = myList.stream().min(Integer::compare);
if(min.isPresent()){
	System.out.println("Minimum element in the List " + min.get());
}

输出:

Minimum element in the List 0
  1. max()根据提供的Comparator返回此流的最大元素。此方法返回一个Optional,描述此流的最大元素。
List<Integer> myList = Arrays.asList(11, 1, 9, 4, 98, 0, 17, 8, 2, 3);
Optional<Integer> max = myList.stream().max(Integer::compare);
if(max.isPresent()){
	System.out.println("Maximum element in the List " + max.get());
}

输出:

Maximum element in the List 98
  1. count()返回此流中元素的数量。
List<Integer> myList = Arrays.asList(11, 1, 9, 4, 98, 0, 17, 8, 2, 3);
long count = myList.stream().count();
System.out.println("Count of elements in the list- " + count);

输出:

Count of elements in the list- 10
  1. findFirst()Java Stream API的findFirst()方法返回描述此流的第一个元素的Optional,如果该流为空,则返回一个空的Optional。
List<Integer> myList = Arrays.asList(11, 1, 9, 4, 98, 0, 17, 8, 2, 3);
Optional<Integer> value = myList.stream().findFirst();
if(value.isPresent()){
	System.out.println("First Element- " + value.get());
}

输出:

First Element- 11

Java Stream中的流管道

数据源(例如Collection,数组,生成器函数或者I / O通道)后跟零个或者多个中间操作和终端操作一起形成流管道。

流管道

流管道示例

在以下示例中,流源是ArrayList。第一个中间操作是映射操作,它向Stream中的每个元素加10,在另一个操作中元素被排序。然后使用forEach终端操作显示元素,这时将消耗流。

List<Integer> myList = Arrays.asList(11, 1, 9, 4, 98, 0, 17, 8, 2, 3);
myList.stream().map(n -> n + 10).sorted().forEach(System.out::println);

集合与流API

流在几个方面与集合不同

  • 没有存储与集合不同,流不是存储元素的数据结构。在流中,来自数据源的元素通过管道移动,同时在每个中间步骤进行计算操作。

  • 本质上是功能性Java Stream API本质上是功能性的,将功能性方法引入到处理对象集合中。对流的操作会产生结果,但不会修改其源。例如,对从集合中获取的流进行过滤会产生一个新的不带过滤元素的流,而不是从源集合中删除元素。

  • 延迟调用Java Stream API中的中间操作总是延迟提供优化机会。

  • 可能无界尽管集合的大小是有限的,但流并不需要。诸如limit(n)或者findFirst()之类的短路操作可以允许对无限流的计算在有限时间内完成。

  • 流是可消耗的在流的生命周期中,流的元素仅被访问一次,一旦在流管道中遇到终端操作,就将流视为已消耗。流消耗完后,必须生成新的流以重新访问源中的相同元素。