Java8 Stream新特性详解及实战

简介: Java8 Stream新特性详解及实战

## 背景介绍


在阅读Spring Boot源代码时,发现Java 8的新特性已经被广泛使用,如果再不学习Java8的新特性并灵活应用,你可能真的要out了。为此,针对Java8的新特性,会更新一系列的文章,欢迎大家持续关注。

首先,我们来看一下Spring Boot源代码ConfigFileApplicationListener类中的一段代码:






private List<Profile> getOtherActiveProfiles(Set<Profile> activatedViaProperty) {  return Arrays.stream(this.environment.getActiveProfiles()).map(Profile::new)      .filter((profile) -> !activatedViaProperty.contains(profile))      .collect(Collectors.toList());}

这段代码怎么?够简洁明快吧,如果不使用Java8的新特性,想象一下得多少行代码才能实现?但如果没掌握或不了解Java8的新特性,这段代码读起来是不是很酸爽?

Java 8的API中新增了一个处理集合的抽象概念:Stream,中文称作“流”。它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用起来就像使用SQL语句来对数据库执行查询操作一样。可以让你如行云流水一般写出简单、高效、干劲的代码。


## 什么是Stream

Stream 中文称为 ,通过将集合转换为这么一种叫做 “流” 的元素序列(注意是抽象概念),通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。通俗来说就是你只用告诉“流”你需要什么,便在出口处等待结果接口。image.png上图为Steam操作的基本流程,在后面的学习过程中可反复与具体的代码进行对照,加深学习印象。


## Stream相关概念

Stream操作的过程中涉及到一些相关概念,先了解一下,方便后面统一称谓。

  • 元素:特定类型的对象,比如List里面放置的对象,会形成一个队列。Stream不会存储元素,只是按需计算。
  • 数据源:流的来源,对照上图中的集合,数组,I/O channel产生器generator等。
  • 聚合操作:类似SQL语句的各种过滤操作,对照上图中的filtersortedmap等。
  • Pipelining:中文词义流水线,中间操作会返回流本身,跟我们之前所说的流式(fluent)编程一个概念,这样可对操作进行优化,比如延迟执行(laziness)和短路(short-circuiting)
  • 内部迭代:传统遍历方式是通过IteratorFor-Each来完成,这是外部迭代。而Stream通过访问者模式(Visitor)实现了内部迭代。

需要注意的是在整个操作的过程中,聚合操作部分可以执行多次操作,但每次操作并不是像传统的集合遍历对集合里面的元素进行转换,而是将操作函数放入一个操作集合中,只有到最后一步(比如for-each打印)时才会一次性执行。

而流和迭代器类似,只能迭代一次。比如,当调用完collect方法之后,流便不能再使用了。

## stream操作方法分类

中间聚合操作:map (mapToInt, flatMap ) filter distinct sorted peek skip parallel sequential unordered

最终输出操作:forEach forEachOrdered toArray reduce collect min max countiterator

短路操作:anyMatch allMatch noneMatch findFirst findAny limit

## 生成流

Java 8 , 生成流有多种方法:Stream接口的静态工厂方法、集合提供的生成方法和其他特殊的生成方法。


### of方法

Stream接口的静态工厂方法主要通过重载的of方法:



public static<T> Stream<T> of(T... values);public static<T> Stream<T> of(T t)

of方法,其生成的Stream是有限长度的,Stream的长度为其内的元素个数。使用示例代码:



Stream<String> stringStream = Stream.of("公众号");Stream<String> stringsStream = Stream.of("关注","公众号", "程序新视界");


### generator方法

of方法对应的generator方法生成的是无限长度的Stream,其元素是由Supplier接口提供的。


public static<T> Stream<T> generate(Supplier<T> s)

使用generate方法生成的Stream通常用于随机数和常量,或者需要前后元素间维持着某种状态信息的场景。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。

示例代码如下:








Stream<Double> generateDouble = Stream.generate(Math::random);Stream<String> generateString = Stream.generate(new Supplier<String>() {      @Override      public String get() {      return "公众号:程序新视界";      }});

其实上面两种写法的效果是一样的,只不过第一种采用了Lambda表达式,简化了代码。


### iterate方法

iterate方法生成的也是无限长度的Stream,是通过函数f迭代对给指定的元素种子而产生无限连续有序Stream,其中包含的元素可以认为是:seedf(seed),f(f(seed))无限循环。示例代码如下:


Stream.iterate(1,i -> i +1).limit(10).forEach(System.out::println);

打印结果为12345678910

上面的方法可以认为种子(seed)为1f(seed)为在1的基础上“+1”,依次循环下去,直到达到limit的限制,最后生成对应的Stream

### empty方法

empty方法生成一个空的Stream,不包含任何元素。

### Collection接口和数组的默认方法

Collection接口和数组中都提供了默认的生成Stream的方法。直接看源代码:
















// Collection中default Stream<E> stream() {    return StreamSupport.stream(spliterator(), false);}// 并行流操作default Stream<E> parallelStream() {    return StreamSupport.stream(spliterator(), true);}// Arrays中public static <T> Stream<T> stream(T[] array) {    return stream(array, 0, array.length);}public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {    return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);}


示例代码:










List<String> list = new ArrayList<>();list.add("欢迎关注");list.add("微信公众号");list.add("程序新视界");list.stream().forEach(System.out::println);
int[] nums = new int[]{1, 2, 3, 4, 5};Arrays.stream(nums).forEach(System.out::println);

### 其他生成方法

关于其他生成方法就不详细举例了,比如:Random.ints()BitSet.stream()JarFile.stream()Pattern.splitAsStream(java.lang.CharSequence)Files.lines(java.nio.file

.Path)等。


## 操作方法使用

关于操作方法就不进行详细的讲解,更多的以示例的形式展示如何使用。

### concat方法

合并两个Stream。如果输入Stream有序,则新Stream有序;如果其中一个Stream为并行,则新Stream为并行;如果关闭新Stream,原Stream都将执行关闭。


Stream.concat(Stream.of("欢迎","关注"),Stream.of("程序新视界")).forEach(System.out::println);


### distinct

Stream中元素去重。


Stream.of(1,1,1).distinct().forEach(System.out::println);

打印结果为1

### filter

根据指定条件进行筛选过滤,留下满足条件的元素。


Stream.of(1, 2, 3, 4, 5).filter(i -> i >= 3).forEach(System.out::println);

打印结果为345

### map

Stream中的元素进行映射转换,比如将“a”转为“A”,期间生产了新的Stream。同时为了提升效率,官方也提供了封装好的方法:mapToDoublemapToIntmapToLong



Stream.of("a","b","c").map(item -> item.toUpperCase()).forEach(System.out::println);Stream.of("a","b","c").map(String::toUpperCase).forEach(System.out::println);

打印结果为ABC

### flatMap

将流中的每一个元素映射为一个流,再把每一个流连接成为一个流。期间原有的Stream的元素会被逐一替换。官方提供了三种原始类型的变种方法:flatMapToIntflatMapToLongflatMapToDouble


Stream.of(1, 2, 3).flatMap(i -> Stream.of(i * 10)).forEach(System.out::println);

打印结果为102030

### peek

生成一个相同的Stream,并提供一个消费函数,当新Stream中的元素被消费(执行操作)时,该消费函数会在此之前先执行。


Stream.of(1, 2).peek(i -> System.out.println("peekCall:" + i)).forEach(System.out::println);

打印结果依次为:peekCall:11peekCall:22

### ship

跳过前N个元素,取剩余元素,如果没有则为空Stream


Stream.of(1, 2, 3).skip(2).forEach(System.out::println);

打印结果为3

### sorted

Stream元素进行排序,可采用默认的sorted()方法进行排序,也可通过sorted(Comparator)方法自定义比较器来进行排序,前者默认调用equals方法来进行比较。


Stream.of(1, 3, 2).sorted().forEach(System.out::println);

打印结果:123

### limit

限制返回前N个元素,与SQL中的limit相似。


Stream.of(1, 2, 3).limit(2).forEach(System.out::println);

打印结果为:12

### collect

收集方法,实现了很多归约操作,比如将流转换成集合和聚合元素等。



Stream.of(1, 2, 3).collect(Collectors.toList());Stream.of(1, 2, 3).collect(Collectors.toSet());

除了以上的集合转换,还有类似joining字符串拼接的方法,具体可查看Collectors中的实现。

### count

返回Stream中元素个数。


Stream.of(1, 2, 3).count();

### forEach

遍历Stream中所有元素。示例参考以上设计到的。

### forEachOrder

遍历Stream中所有元素,如果Stream设置了顺序,则按照顺序执行(Stream是无序的),默认为元素的插入顺序。

### max

根据指定的比较器(Comparator),返回Stream中最大元素的Optional对象,Optional中的value便是最大值。

Optional可以代表一个值或不存在,主要是为了规避返回值为null,而抛出NullPointerException的问题,也是由Java8引入的。但当调用其get()方法时,如果当前值不存在则会抛出异常。



Optional<Integer> max = Stream.of(1, 2, 3).max(Comparator.comparingInt(o -> o));System.out.println("max:" + max.get());

打印结果:max:3

### min

max操作相同,功能相反,取最小值。



Optional<Integer> min = Stream.of(1, 2, 3).min(Comparator.comparingInt(o -> o));System.out.println("min:" + min.get());

打印结果:min:1

### reduce

reduce可实现根据指定的规则从Stream中生成一个值,比如之前提到的countmaxmin方法是因为常用而被纳入标准库中。实际上,这些方法都是reduce的操作。



Stream.of(1, 2, 3).reduce(Integer::sum);Stream.of(1, 2, 3).reduce(0, (a, b) -> a + b);

以上两个方法都是对结果进行求和,不同的是第一个方法调用的是reducereduce((T, T) -> T)方法,而第二个调用的是reduce(T, (T, T) -> T)。其中第二个方法的第一个参数0,表示从第0个值开始操作。

### allMatch

判断Stream中的所有元素是否满足指定条件。全部满足返回true,否则返回false



boolean result = Stream.of(1, 2, 3).allMatch(i  -> i > 0);System.out.println(result);

返回结果:true

### anyMatch

判断Stream中的元素至少有一个满足指定条件。如果至少有一个满足则返回true,否则返回false



boolean anyResult = Stream.of(1, 2, 3).anyMatch(i  -> i > 2);System.out.println(anyResult);

返回结果:true

### findAny

获得其中一个元素(使用stream()时找到的是第一个元素;使用parallelStream()并行时找到的是其中一个元素)。如果Stream为空,则返回一个为空的Optional



Optional<String> any = Stream.of("A", "B", "C").findAny();System.out.println(any.get());

返回结果:A

### findFirst

获得第一个元素。如果Stream为空,则返回一个为空的Optional



Optional<String> first = Stream.of("A", "B", "C").findFirst();System.out.println(first.get());

返回结果:A

### noneMatch

判断Stream中是否所有元素都不满足指定条件。都不满足则返回true,否则false



boolean noneMatch = Stream.of(1, 2, 3).noneMatch(i  -> i > 5);System.out.println(noneMatch);

返回结果:true

### 统计

通过summaryStatistics方法可获得Stream的一些统计信息。






IntSummaryStatistics summaryStatistics = Stream.of(1, 2, 3).mapToInt((i) -> i).summaryStatistics();System.out.println("max:" + summaryStatistics.getMax());System.out.println("min:" + summaryStatistics.getMin());System.out.println("sum:" + summaryStatistics.getSum());System.out.println("average:" + summaryStatistics.getAverage());

这里用到了流与数值流直接的转换mapToInt,类似的方法还有mapToDoublemapToLong。对应获得的数值流还提供了一些额外的方法,就像上面获取不同统计信息的方法一样。


## 鸟瞰Stream


最后,我们再来看一下Stream的主要接口类关系图。

image.png其中,BaseStream定义了Stream的基本接口,Stream中定义了mapfilterflatMap等常用操作。IntStreamLongStreamDoubleStream是针对基本类型提供了便捷和特化操作。以上接口构建了Java8中流体系的根基。AbstractPipeline是流水线(Pipeline)的核心抽象类,用于构建和管理流水线。

另外,关于Stream的效率问题网上也有很多资料提到,下一篇文章,我们将重点说说Stream的性能问题,关注公众号程序新视界,敬请期待。

目录
相关文章
|
1天前
|
Java API
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
|
2天前
|
Java 编译器 开发者
Java一分钟之-继承:复用与扩展类的特性
【5月更文挑战第9天】本文探讨了Java中的继承机制,通过实例展示了如何使用`extends`创建子类继承父类的属性和方法。文章列举了常见问题和易错点,如构造器调用、方法覆盖、访问权限和类型转换,并提供了解决方案。建议深入理解继承原理,谨慎设计类结构,利用抽象类和接口以提高代码复用和扩展性。正确应用继承能构建更清晰、灵活的代码结构,提升面向对象设计能力。
9 0
|
4天前
|
存储 监控 安全
JVM工作原理与实战(十六):运行时数据区-Java虚拟机栈
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了运行时数据区、Java虚拟机栈等内容。
11 0
|
5天前
|
Java
Java中的多线程编程:基础知识与实战技巧
【5月更文挑战第6天】多线程编程是Java中的一个重要特性,它允许我们在一个程序中同时执行多个任务。本文将介绍Java多线程的基础知识,包括线程的创建、启动、同步和通信,以及如何在Java中实现多线程编程。通过实例代码和解析,帮助读者深入理解Java多线程编程的概念和应用。
|
6天前
|
安全 Java 程序员
Java 8新特性之Lambda表达式
【5月更文挑战第5天】 本文将介绍Java 8中的一个重要新特性——Lambda表达式。Lambda表达式是Java 8引入的一种简洁、易读的函数式编程语法,它允许我们将函数作为参数传递给方法,或者作为返回值。通过使用Lambda表达式,我们可以编写更简洁、更易读的代码,提高开发效率。
|
9天前
|
存储 Java 数据格式
Java实战:轻松掌握文件重命名与路径提取技巧
Java实战:轻松掌握文件重命名与路径提取技巧
16 0
|
10天前
|
分布式计算 Java API
Java 8新特性之Lambda表达式与Stream API
【5月更文挑战第1天】本文将介绍Java 8中的两个重要特性:Lambda表达式和Stream API。Lambda表达式是一种新的函数式编程语法,可以简化代码并提高可读性。Stream API是一种用于处理集合的新工具,可以方便地进行数据操作和转换。通过结合Lambda表达式和Stream API,我们可以更加简洁高效地编写Java代码。
|
11天前
|
供应链 Java API
Java 8新特性解析及应用区块链技术在供应链管理中的应用与挑战
【4月更文挑战第30天】本文将深入探讨Java 8的新特性,包括Lambda表达式、Stream API和Optional类等。通过对这些新特性的详细解析和应用实例,帮助读者更好地理解和掌握Java 8的新技术。
|
11天前
|
设计模式 算法 安全
Java多线程编程实战:从入门到精通
【4月更文挑战第30天】本文介绍了Java多线程编程的基础,包括线程概念、创建线程(继承`Thread`或实现`Runnable`)、线程生命周期。还讨论了线程同步与锁(同步代码块、`ReentrantLock`)、线程间通信(等待/通知、并发集合)以及实战技巧,如使用线程池、线程安全设计模式和避免死锁。性能优化方面,建议减少锁粒度和使用非阻塞算法。理解这些概念和技术对于编写高效、可靠的多线程程序至关重要。
|
11天前
|
Java
Java8 Stream Collectors groupingBy使用
Java8 Stream Collectors groupingBy使用