Java8新特性Stream流的详细介绍及常用操作

目录

1.什么是流

2.构建流

2.1由值构建流

2.2由数组创建流

2.3由文件生成流

2.4由函数生成流:创建无限流

2.4.1迭代

2.4.2生成

3.流操作

3.1中间操作

3.2终端操作

3.3 实例

4.收集数据

4.1归约

4.1.1求和

4.1.2最大值:

4.1.3最小值:

4.2汇总

4.2.1常用汇总方法

4.2.2字符串连接



1.什么是流

Java8对流的定义就是“从支持数据处理操作的源生成的元素序列”。

  • 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如 ArrayList 与 LinkedList )。但流的目的在于表达计算,比如filter 、 sorted 和 map 。集合讲的是数据,流讲的是计算。
  • 源——流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
  • 数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以顺序执行,也可并行执行。此外,流操作有两个重要的特点。
  • 流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。这让我们下一章中的一些优化成为可能,如延迟和短路。流水线的操作可以看作对数据源进行数据库式查询。
  • 内部迭代——与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。你可以从原始数据源那里再获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集合之类的可重复的源,如果是I/O通道就没戏了)。例如,以下代码会抛出一个异常,说流已被消费掉了:

List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);

2.构建流

流不仅可以从集合创建,也可从值、数组、文件以及 iterate 与 generate 等特定方法创建,还可以构建没有固定大小的无限流

2.1由值构建流

你可以使用静态方法 Stream.of ,通过显式值创建一个流。它可以接受任意数量的参数。例如,以下代码直接使用 Stream.of 创建了一个字符串流。然后,你可以将字符串转换为大写,再
一个个打印出来:

Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);

你可以使用 empty 得到一个空流,如下所示:

Stream<String> emptyStream = Stream.empty();

2.2由数组创建流

你可以使用静态方法 Arrays.stream 从数组创建一个流。它接受一个数组作为参数。例如,你可以将一个原始类型 int 的数组转换成一个 IntStream ,如下所示:

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum(); //结果是41

2.3由文件生成流

Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。java.nio.file.Files 中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines ,它会返回一个由指定文件中的各行构成的字符串流。使用你迄今所学的内容,你可以用这个方法看看一个文件中有多少各不相同的词:

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){

}
//这里使用的是try-with-resources语句优雅的关闭资源的方式,java7的新特性

你可以使用 Files.lines 得到一个流,其中的每个元素都是给定文件中的一行。然后,你可以对 line 调用 split 方法将行拆分成单词。应该注意的是,你该如何使用 flatMap 产生一个扁平的单词流,而不是给每一行生成一个单词流。最后,把 distinct 和 count 方法链接起来,数数流中有多少各不相同的单词。

2.4由函数生成流:创建无限流

Stream API提供了两个静态方法来从函数生成流: Stream.iterate 和 Stream.generate 。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由 iterate和 generate 产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用 limit(n) 来对这种流加以限制,以避免打印无穷多个值。

2.4.1迭代

我们先来看一个 iterate 的简单例子,然后再解释:

Stream.iterate(0, n -> n + 2)
      .limit(10)
      .forEach(System.out::println);

iterate 方法接受一个初始值(在这里是 0 ),还有一个依次应用在每个产生的新值上的Lambda( UnaryOperator<t> 类型)。这里,我们使用Lambda n -> n + 2 ,返回的是前一个元素加上2。因此, iterate 方法生成了一个所有正偶数的流:流的第一个元素是初始值 0 。然后加上 2 来生成新的值 2 ,再加上 2 来得到新的值 4 ,以此类推。这种 iterate 操作基本上是顺序的,因为结果取决于前一次应用。请注意,此操作将生成一个无限流——这个流没有结尾,因为值是按需计算的,可以永远计算下去。我们说这个流是无界的。正如我们前面所讨论的,这是流和集合之间的一个关键区别。我们使用 limit 方法来显式限制流的大小。这里只选择了前10个偶数。然后可以调用 forEach 终端操作来消费流,并分别打印每个元素。

2.4.2生成

与 iterate 方法类似, generate 方法也可让你按需生成一个无限流。但 generate 不是依次对每个新生成的值应用函数的。它接受一个 Supplier<T> 类型的Lambda提供新的值。我们先来看一个简单的用法:

Stream.generate(Math::random)
      .limit(5)
      .forEach(System.out::println);

这段代码将生成一个流,其中有五个0到1之间的随机双精度数。例如,运行一次得到了下面的结果:

0.9410810294106129
0.6586270755634592
0.9592859117266873
0.13743396659487006
0.3942776037651241

Math.Random 静态方法被用作新值生成器。同样,你可以用 limit 方法显式限制流的大小,否则流将会无限长。

 IntStream 的 generate 方法会接受一个 IntSupplier ,而不是 Supplier<t> 。例如,可以这样来生成一个全是1的无限流:

IntStream ones = IntStream.generate(() -> 1);

Lambda允许你创建函数式接口的实例,只要直接内联提供方法的实现就可以。你也可以像下面这样,通过实现 IntSupplier 接口中定义的 getAsInt 方法显式传递一个对象:

IntStream twos = IntStream.generate(new IntSupplier(){
    public int getAsInt(){
    return 2;
    }
});

generate 方法将使用给定的供应源,并反复调用 getAsInt 方法,而这个方法总是返回 2 。

斐波纳契数列是著名的经典编程练习。下面这个数列就是斐波纳契数列的一部分:0, 1, 1,2, 3, 5, 8, 13, 21, 34, 55…数列中开始的两个数字是0和1,后续的每个数字都是前两个数字之和。斐波纳契元组序列与此类似,是数列中数字和其后续数字组成的元组构成的序列:(0, 1),(1, 1), (1, 2), (2, 3), (3, 5), (5, 8), (8, 13), (13, 21) …,下面的代码就是如何创建一个在调用时返回下一个斐波纳契项的IntSupplier :

IntSupplier fib = new IntSupplier(){
private int previous = 0;
private int current = 1;
public int getAsInt(){
    int oldPrevious = this.previous;
    int nextValue = this.previous + this.current;
    this.previous = this.current;
    this.current = nextValue;
    return oldPrevious;
    }
};
IntStream.generate(fib).limit(10).forEach(System.out::println);

3.流操作

流的使用一般包括三件事:

  • 一个数据源(如集合)来执行一个查询;
  • 一个中间操作链,形成一条流的流水线;
  • 一个终端操作,执行流水线,并能生成结果

3.1中间操作

中间操作
操作返回类型使用的类型/函数式接口函数描述符解释
filterStream<T>Predicate<T>T->boolean根据谓词删选之后,返回筛选之后的流
mapStream<R>Function<T, R>T->R接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素
flatmapStream<R>Function<T, Stream<R>>T->Stream<R>把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流
distinctStream<T>  返回一个元素各异(根据流所生成元素的
hashCode 和 equals 方法实现)的流
sortedStream<T>Comparator<T>int compare(T o1, T o2)返回一个排序后的流,可以在传入条件的时候使用reversed()方法反转排序结果
limitStream<T>long 截短流,返回前 n 个元素的流
skipStream<T>long 跳过流,返回一个扔掉了前 n 个元素的流

3.2终端操作

终端操作
操作返回类型使用的类型/函数式接口函数描述符解释
anyMatchbooleanPredicate<T>T -> boolean检查谓词是否至少匹配一个元素
noneMatchbooleanPredicate<T>T -> boolean检查流中没有任何元素与给定的谓词匹配
allMatchbooleanPredicate<T>T -> boolean检查谓词是否匹配所有元素
findAnyOptional<T>  返回当前流中的任意元素
findFirstOptional<T>  返回流中的第一个元素
forEachvoidConsumer<T>T -> void消费流中的每一个元素(无返回值)
collectRCollector<T, A, R> 接受各种方案作为参数,并将流中的元素累积成为一个汇总结果的操作
reduceOptional<T>BinaryOperator<T>(T, T) -> T把一个流中的元素组合起来,归约成一个值
countlong  统计流中元素的个数

3.3 实例

String[] words = {"Hello", "World"};
List<String> uniqueCharacters =
          Arrays.stream(words)
                        .map(w -> w.split(""))
                        .flatMap(Arrays::stream)
                        .distinct()
                        .collect(Collectors.toList());

uniqueCharacters.stream().forEach(System.out::println);

 

使用 flatMap 方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用 map(Arrays::stream) 时生成的单个流都被合并起来,即扁平化为一个流。

使用 flatMap 方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用 map(Arrays::stream) 时生成的单个流都被合并起来,即扁平化为一个流。

4.收集数据

4.1归约

规约的reduce 接受两个参数:

  • 一个初始值,这里是0;
  • 一个 BinaryOperator<T> 来将两个元素结合起来产生一个新值。

4.1.1求和

int sum = numbers.stream().reduce(0, (a, b) -> a + b);

numbers 中的每个元素都用加法运算符反复迭代来得到结果。通过反复使用加法,你把一个数字列表归约成了一个数字。首先, 0 作为Lambda( a )的第一个参数,从流中获得 4 作为第二个参数( b )。 0 + 4 得到 4 ,它成了新的累积值。然后再用累积值和流中下一个元素 5 调用Lambda,产生新的累积值 9 。接下来,再用累积值和下一个元素 3调用Lambda,得到 12 。最后,用 12 和流中最后一个元素 9 调用Lambda,得到最终结果 21 。

无初始值
reduce 还有一个重载的变体,它不接受初始值,但是会返回一个 Optional 对象:

Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));

考虑流中没有任何元素的情况。 reduce 操作无法返回其和,因为它没有初始值。这就是为什么结果被包裹在一个 Optional 对象里,以表明和可能不存在。

一般的reduce操作总结:

4.1.2最大值:

Optional<Integer> max = numbers.stream().reduce(Integer::max);

4.1.3最小值:

Optional<Integer> min = numbers.stream().reduce(Integer::min);

等价于Optional<Integer> min = numbers.stream().reduce((x, y) -> x < y ? x : y);

4.2汇总

4.2.1常用汇总方法

 Collectors.summingInt 。它可接受一个把对象映射为求和所需 int 的函数,并返回一个收集器;该收集器在传递给普通的 collect 方法后即执行我们需要的汇总操作。汇总还有其他操作,如 Collectors.averagingInt,averagingLong,averagingDouble 可以计算数值的平均数。

但有时候我们需要找到元素数值属性的最大值和最小值,以及计算其总和和平均值。那么只需一次操作就可以完成。在这种情况下,你可以使用 summarizingInt 工厂方法返回的收集器。

IntSummaryStatistics menuStatistics =
menu.stream().collect(summarizingInt(Dish::getCalories));

这个收集器会把所有这些信息收集到一个叫作 IntSummaryStatistics 的类里,它提供了方便的取值(getter)方法来访问结果。打印 menuStatisticobject 会得到以下输出:

IntSummaryStatistics{count=9, sum=4300, min=120,
average=477.777778, max=800}

同样,相应的 summarizingLong 和 summarizingDouble 工厂方法有相关的 LongSummaryStatistics 和 DoubleSummaryStatistics 类型,适用于收集的属性是原始类型 long 或double 的情况。

4.2.2字符串连接

joining 工厂方法返回的收集器会把对流中每一个对象应用 toString 方法得到的所有字符串连接成一个字符串。

        String[] words = {"Hello", "World"};
        String collect = Arrays.stream(words).collect(Collectors.joining("-"));
        System.out.println(collect);

最后一个方法是最详细的,三个参数分别为分隔符,前缀和后缀。

 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8引入了Stream API,它是一种新的处理集合的方式,可以用更简洁、更易读的代码处理集合数据。Stream API提供了非常方便的、高效的数据处理方式,包括筛选、排序、映射、归约等。 下面是一些Stream常用操作: 1. filter():筛选符合条件的元素 ```java List<String> list = Arrays.asList("apple", "orange", "banana", "pear", "peach"); List<String> result = list.stream().filter(str -> str.contains("e")).collect(Collectors.toList()); ``` 2. map():将元素转换成其他形式或提取信息 ```java List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5); List<Integer> result = nums.stream().map(num -> num * num).collect(Collectors.toList()); ``` 3. sorted():对元素进行排序 ```java List<Integer> nums = Arrays.asList(5, 3, 1, 2, 4); List<Integer> result = nums.stream().sorted().collect(Collectors.toList()); ``` 4. distinct():去重 ```java List<Integer> nums = Arrays.asList(1, 2, 3, 3, 4, 4, 5); List<Integer> result = nums.stream().distinct().collect(Collectors.toList()); ``` 5. limit():截取中前n个元素 ```java List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5); List<Integer> result = nums.stream().limit(3).collect(Collectors.toList()); ``` 6. skip():跳过中前n个元素 ```java List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5); List<Integer> result = nums.stream().skip(3).collect(Collectors.toList()); ``` 7. reduce():将中元素归约为一个值 ```java List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5); int result = nums.stream().reduce(0, (a, b) -> a + b); ``` 这些操作只是Stream API中的一部分,还有很多其他操作可以使用。Stream API可以让我们更加方便地处理集合数据,提高开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值