Stream是Java8引入的一个重度使用lambda表达式的API。
Stream可以用流的方式处理数据集合,在Java8之前,我们处理这些集合,是需要迭代器的,比如iterator,这是外部迭代;而Stream是内部迭代,我们不用关心集合内部元素是如何迭代的,计算机会自动帮我们选择最适合的实现方式。
如何创建一个流
- 最常见的,有一个集合对象
List<String> strs = Arrays.asList("Java 8 ", "Lambdas ", "In ", "Action");
,直接调用strs.stream()
就得到一个Stream<String>
的流。
如果想使用并行流增加性能,请使用strs.parallelStream()
,或strs.stream().parallel()
。 - 由值创建:
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
由数组创建:
12int[] numbers = {2, 3, 5, 7, 11, 13};int sum = Arrays.stream(numbers).sum();由文件创建:
12345678// 统计文本文件中有多少个不同的单词long uniqueWords = 0;try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct().count();} catch (IOException e) {}由函数生成流:
Stream.iterate()
和Stream.generate()
可以生产无限流,即元素有无穷多个。一般来说,应该使用limit(n)来对这种流加以限制,以避免产生无穷多个元素。1234567public void iterator() {Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);}public void generate() {Stream.generate(Math::random).limit(5).forEach(System.out::println);}
Stream常用方法
Stream API 支持两种类型的操作:中间操作(如filter或map)和终端操作(如count、findFirst、forEach和reduce)。
我用一个筛选菜单的需求作为示例。
准备工作
|
|
|
|
过滤筛选
谓词:返回boolean的函数
filter():接受一个谓词,返回符合条件的元素集合
12345678public void filter() {List<Dish> menu = init();List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(Collectors.toList());Assert.assertEquals(4, vegetarianMenu.size());}distinct():返回集合中各异的元素集合(去重)
12345public void distinct() {List<Integer> numbers = Arrays.asList(5, 1, 2, 1, 3, 3, 2, 4);numbers.stream().distinct().forEach(System.out::println);}limit():截取流中指定数量的元素,返回一个不超过给定长度的流。如果流是有序的,则最多会返回前n个元素。
123456789``` javapublic void limit() {List<Dish> menu = init();menu.stream().filter(d -> d.getCalories() > 300).limit(3).forEach(System.out::println);}skip():跳过指定数量元素,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。
123456789public void skip() {List<Dish> menu = init();menu.stream().filter(d -> d.getCalories() > 300).limit(3).skip(2).forEach(System.out::println);}
映射
map():接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。
12345public void map() {List<Dish> menu = init();List<String> dishNames = menu.stream().map(m -> m.getName()).collect(Collectors.toList());}flatMap():一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流,即扁平化为一个流。
12345678910public void flatMap() {String[] arrayOfWords = {"Goodbye", "World"};List<String> words = Arrays.asList(arrayOfWords);words.stream().map(w -> w.split("")).flatMap(Arrays::stream).distinct().forEach(System.out::println);}
上面例子中,split()
得到的是String[] 而不是String,因此各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,变为一个流。
匹配
匹配比较简单,返回一个boolean
- anyMatch():至少匹配一个
- allMatch():全部匹配
- noneMatch():全部不匹配,和allMatch相反
|
|
查找
查找有2个方法:findFirst()
和findAny()
,返回一个Optional<T>
集合。
如果你不关心返回的元素是哪个,请使用findAny()
,因为它在使用并行流时限制较少。
归约
归约在汇总结合内所有数据的时候使用。比如求 max,min,sum。
原始类型流特化
流在内部迭代的过程中,对基本类型会自动装箱和拆箱。为了避免不需要的装箱拆箱,Java8提供了IntStream
、DoubleStream
和LongStream
- 普通流转特化流:mapToInt(), mapToLong(), mapToDouble()
- 特化流转普通流:boxed()
|
|
Java 8引入了两个可以用于IntStream和LongStream的静态方法,用于生成给定范围的数字流:
- range(min, max):随机生成的数字不包含max,即(min, max)
- rangeClosed(min, max):随机生成的数字包含max,即(min, max]