Java Stream API示例
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。
中间流操作示例
- 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
- 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
- flatMap()flatMap()操作对流的元素进行一对多转换,还将生成的结构展平为新的流。如果对象包含许多嵌套对象,则flatMap()操作通过展平结构将所有嵌套级别的对象都置于同一级别。
如果要生成包含在数组的字符串中的单词流。
Stream<String> lineStream = Arrays.stream(lines); Stream<String> str = lineStream.flatMap(line -> Stream.of(line.split(" +")));
- 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]
- 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
- 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。
终端流操作示例
- forEach()在forEach方法中,将Consumer作为参数传递,并且此Consumer操作在此流的每个元素上执行。
List<String> myList = Arrays.asList("rose", "lotus", "lily", "orchid"); myList.stream().forEach(System.out::println);
输出:
rose lotus lily orchid
- 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
- 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]
- 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
- 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
- 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
- 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()之类的短路操作可以允许对无限流的计算在有限时间内完成。
流是可消耗的在流的生命周期中,流的元素仅被访问一次,一旦在流管道中遇到终端操作,就将流视为已消耗。流消耗完后,必须生成新的流以重新访问源中的相同元素。