【java8新特性】——Stream API详解(二)

一、简介

java8新添加了一个特性:流Stream。Stream让开发者能够以一种声明的方式处理数据源(集合、数组等),它专注于对数据源进行各种高效的聚合操作(aggregate operation)和大批量数据操作 (bulk data operation)。

Stream API将处理的数据源看做一种Stream(流),Stream(流)在Pipeline(管道)中传输和运算,支持的运算包含筛选、排序、聚合等,当到达终点后便得到最终的处理结果。

几个关键概念:

  1. 元素 Stream是一个来自数据源的元素队列,Stream本身并不存储元素。
  2. 数据源(即Stream的来源)包含集合、数组、I/O channel、generator(发生器)等。
  3. 聚合操作 类似SQL中的filter、map、find、match、sorted等操作
  4. 管道运算 Stream在Pipeline中运算后返回Stream对象本身,这样多个操作串联成一个Pipeline,并形成fluent风格的代码。这种方式可以优化操作,如延迟执行(laziness)和短路( short-circuiting)。
  5. 内部迭代 不同于java8以前对集合的遍历方式(外部迭代),Stream API采用访问者模式(Visitor)实现了内部迭代。
  6. 并行运算 Stream API支持串行(stream() )或并行(parallelStream() )的两种操作方式。

Stream API的特点:

  1. Stream API的使用和同样是java8新特性的lambda表达式密不可分,可以大大提高编码效率和代码可读性。
  2. Stream API提供串行和并行两种操作,其中并行操作能发挥多核处理器的优势,使用fork/join的方式进行并行操作以提高运行速度。
  3. Stream API进行并行操作无需编写多线程代码即可写出高效的并发程序,且通常可避免多线程代码出错的问题。

二、简单示例

我们来看一个简单的示例,统计整数数组中正数的个数:

1.在java8之前:
    public static void main(String[] args){  List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);long count = 0;for(Integer number: numbers){if(number > 0){count++;}}System.out.println("Positive count: " + count);}
2.在java8之后:
    public static void main(String[] args){  List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);long count = numbers.parallelStream().filter(i -> i>0).count();System.out.println("Positive count: " + count);}

可以看到,上例中,使用filter()方法对数组进行了过滤,使用count()方法对过滤后的数组进行了大小统计,且使parallelStream()方法为集合创建了并行流,自动采用并行运算提高速度。在更复杂的场景,还可以用forEach()、map()、limit()、sorted()、collect()等方法进行进一步的流运算。

三、典型接口详解

本节以典型场景为例,列出Stream API常用接口的用法,并附上相应代码。
需要说明的是,Stream API中存在很多方法重载,同名方法本文中可能仅列举一个,请读者注意~

3.1 Stream的生成

java8 Stream API支持串行或并行的方式,可以简单看下jdk1.8 Collection接口的源码(注释只截取部分):

    /*** @return a sequential {@code Stream} over the elements in this collection* @since 1.8*/default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}/*** @return a possibly parallel {@code Stream} over the elements in this collection* @since 1.8*/default Stream<E> parallelStream() {return StreamSupport.stream(spliterator(), true);}

可以看出,在集合类的接口(Collection)中,分别用两种方式来生成:

  1. 串行流 : stream()
  2. 并行流 : parallelStream()

应该注意的是,使用parallelStream()生成并行流后,对集合元素的遍历是无序的。

3.2 forEach()方法

简单看下forEach()方法的源码(注释只截取部分):

    /*** Performs an action for each element of this stream.*/void forEach(Consumer<? super T> action);

forEach()方法的参数为一个Consumer(消费函数,一个函数式接口)对象,forEach()方法用来迭代流中的每一个数据,例如:

    public static void main(String[] args){  List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);numbers.stream().forEach(n ->  System.out.println("List element: " + n));}

上例中,对数组的每个元素进行串行遍历,并打印每个元素的值。

ps:

集合的顶层接口Iterable中也投forEach方法,可以直接对数组元素进行遍历:

    public static void main(String[] args){  List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);numbers.forEach(n ->  System.out.println("List element: " + n));}

当然用Strem API的好处不仅仅是遍历~~~

3.3 map()方法

简单看下map()方法的源码(注释只截取部分):

    /*** Returns a stream consisting of the results of applying the given function to the elements of this stream.* @param <R> The element type of the new stream* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,*               <a href="package-summary.html#Statelessness">stateless</a>*               function to apply to each element* @return the new stream*/<R> Stream<R> map(Function<? super T, ? extends R> mapper);

map()方法的参数为Function(函数式接口)对象,map()方法将流中的所有元素用Function对象进行运算,生成新的流对象(流的元素类型可能改变)。举例如下:

    public static void main(String[] args){  List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);numbers.stream().map( n -> Math.abs(n)).forEach(n ->  System.out.println("Element abs: " + n));}

上例中,用map()方法计算了所有数组元素的绝对值并生成了一个新的流,然后再用forEach()遍历打印。

3.4 flatMap()方法

简单看下flatMap()方法的源码(省略注释):

 <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

显然,跟map()方法不同的是,Function函数的返回值类型是Stream<? extends R>类型,而不是R类型,即Function函数返回一个Stream流,这样flatMap()能够将一个二维的集合映射成一个一维的集合,比map()方法拥有更高的映射深度(此处可能有一点绕,可结合例子理解),作个简单示例如下:

有一个字符串数组:

List<String> list = Arrays.asList("1 2", "3 4", "5 6");

其有三个元素,每个元素有两个数组并用空格隔开,如果每个元素以空格分割成2个元素,并遍历打印这6个元素,

用flatMap()方法如下:

list.stream().flatMap(item -> Arrays.stream(item.split(" "))).forEach(System.out::println);

而用map()方法:

 list.stream().map(item -> Arrays.stream(item.split(" "))).forEach(n ->n.forEach(System.out::println));

可见,用map()方法,返回了一个“流中流”,需要在每个Stream元素遍历时,再加一层forEach进行遍历。

3.5 filter()方法

简单看下filter()方法的源码(注释只截取部分):
    /*** Returns a stream consisting of the elements of this stream that match the given predicate.** <p>This is an <a href="package-summary.html#StreamOps">intermediate operation</a>.** @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,*                  <a href="package-summary.html#Statelessness">stateless</a>*                  predicate to apply to each element to determine if it  should be included* @return the new stream*/Stream<T> filter(Predicate<? super T> predicate);

filter()方法的参数为Predicate(函数式接口)对象,再lambda表达式的讲解中我们提到过这个接口,一般用它进行过滤。正如第二章中示例:

    public static void main(String[] args){  List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);long count = numbers.parallelStream().filter(i -> i>0).count();System.out.println("Positive count: " + count);}

用filter方法很容易过滤出整数数组中的自然数。

3.6 reduce()方法

reduce操作又称为折叠操作,用于将流中的所有值合成一个。reduce()方法的源码(不提供计算初始值的reduce方法)(省略注释):

Optional<T> reduce(BinaryOperator<T> accumulator);

reduce()方法参数为BinaryOperator类型的累加器(它接受两个类型相同的参数,返回值类型跟参数类型相同),返回一个Optional对象。
 实际上,Stream API中的mapToInt()方法返回的IntStream接口有类似的 average()、count()、sum()等方法就是做reduce操作,类似的还有mapToLong()、mapToDouble() 方法。当然,我们也可以用reduce()方法来自定义reduce操作。例如我们用reduce()方法来进行整数数组求和操作:

    public static void main(String[] args){List<Integer> numbers = Arrays.asList(-1, -2, 0, -1, 4, 5, 1);Integer total = numbers.stream().reduce((t, n) -> t + n).get();System.out.println("Total: " + total);}

上例中利用reduce()方法结合lambda表达式轻易的实现了数组的求和功能。

3.7 collect()方法

简单看下collect()方法的源码(注释只截取部分):

    /*** @param <R> the type of the result* @param <A> the intermediate accumulation type of the {@code Collector}* @param collector the {@code Collector} describing the reduction* @return the result of the reduction*/<R, A> R collect(Collector<? super T, A, R> collector);

collect()方法的参数为一个java.util.stream.Collector类型对象,可以用java.util.stream.Collectors工具类提供的静态方法来生成,Collectors类实现很多的归约操作,如Collectors.toList()、Collectors.toSet()、Collectors.joining()(joining适用于字符串流)等。看一个简单示例:

    public static void main(String[] args){  List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);List<Integer> abss = numbers.stream().map( n -> Math.abs(n)).collect(Collectors.toList());System.out.println("Abs list: " + abss);}

上例中,用map()方法生成新的流,再用collect()方法返回原数组的绝对值数组。

3.8 summaryStatistics()方法进行数值统计

其实summaryStatistics()方法并不是Stream接口的方法,而是Stream API采用mapToInt()、mapToLong()、mapToDouble()三个方法分别生成IntStream 、LongStream 、DoubleStream 三个接口类型的对象,这个方法的参数分别为3个函数式接口ToIntFunction、ToLongFunction、ToDoubleFunction,使用时可以用lambda表达式计算返回对应的int、long、double类型即可,简单看下这三个方法的源码(省略注释):

    IntStream mapToInt(ToIntFunction<? super T> mapper);LongStream mapToLong(ToLongFunction<? super T> mapper);DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

IntStream 、LongStream 、DoubleStream 三个接口类型都有一个summaryStatistics()方法,其中,

  1. IntStream 的方法是:
 IntSummaryStatistics summaryStatistics();
  1. LongStream 的方法是:
 LongSummaryStatistics summaryStatistics();
  1. DoubleStream 的方法是:
 DoubleSummaryStatistics summaryStatistics();

在IntSummaryStatistics、LongSummaryStatistics 、DoubleSummaryStatistics 三个接口类型(位于java.util包下)中,都有诸如统计数量、最大值、最小值、求和、平均值等方法(方法名和返回类型可能不同),利用这些方法我们可以方便的进行数值统计。以IntSummaryStatistics工具包 为例:

    public static void main(String[] args){List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();System.out.println("Max : " + stats.getMax());System.out.println("Min : " + stats.getMin());System.out.println("Sum : " + stats.getSum());System.out.println("Average : " + stats.getAverage());System.out.println("Count : " + stats.getCount());}

3.9 其它方法

Stream API还有一些其它的方法,比如:
    limit()    获取指定数量的流
    sorted()   对流进行排序
    distinct()  去重
    skip()    跳过指定数量的元素
    peek()   生成一个包含原Stream的所有元素的新Stream,并指定消费函数
    count()   计算元素数量
    …
感兴趣的读者可以阅读源码,读到这里已经很容易理解了,本文不再赘述。

四、注意事项

Stream中的操作从概念上讲分为中间操作和终端操作:

  • 中间操作:例如peek()方法提供Consumer(消费)函数,但执行peek()方法时不会执行Consumer函数,而是等到流真正被消费时(终端操作时才进行消费)才会执行,这种操作为中间操作;
  • 终端操作:例如forEach()、collect()、count()等方法会对流中的元素进行消费,并执行指定的消费函数(peek方法提供的消费函数在此时执行),这种操作为终端操作。
    要理解中间操作和终端操作的概念,防止埋坑~

  • 【java8新特性】——lambda表达式与函数式接口详解(一)

  • 【java8新特性】——Stream API详解(二)

  • 【java8新特性】——Optional详解(三)

  • 【java8新特性】——方法引用(四)

  • 【java8新特性】——默认方法(五)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/557294.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring bean 不被 GC 的真正原因

概述 自从开始接触 Spring 之后&#xff0c;一直以来都在思考一个问题&#xff0c;在 Spring 应用的运行过程中&#xff0c;为什么这些 bean 不会被回收&#xff1f; 今天深入探究了这个问题之后&#xff0c;才有了答案。 思考点 大家都知道&#xff0c;一个 bean 会不会被回…

ad域时间源配置_域控制器server2012时间同步NTP配置

一、域控配置1.修改注册表&#xff0c;设置域控服务器名称2.设置组策略&#xff0c;启动NTP服务器3.域策略中设置windows time服务自动启动二、服务端配置(Ntp服务器&#xff0c;客户端将根据这台服务器的时间进行同步)1、微软键R键&#xff0c;进入“运行”&#xff0c;输入“…

【java8新特性】——Optional详解(三)

一、简介 Optional类是Java8为了解决null值判断问题&#xff0c;借鉴google guava类库的Optional类而引入的一个同名Optional类&#xff0c;使用Optional类可以避免显式的null值判断&#xff08;null的防御性检查&#xff09;&#xff0c;避免null导致的NPE&#xff08;NullPo…

不使用 + 和 - 运算符计算两整数之和

问题概述 不使用运算符 和 -&#xff0c;计算两整数之和 思考 不使用 和 - &#xff0c;那就只能想到用位运算来处理了。思路如下&#xff1a; 两数进行 ^&#xff08;异或运算&#xff09;&#xff0c;可以得到两个数在相同位上数值不同的相加结果两数进行 &&#x…

vts传感器采取船舶的_详解虎门大桥监测系统:传感器与物联网功不可没

来源&#xff1a;传感器专家网近日&#xff0c;虎门大桥“虎躯一震”给全国人民来了个“深呼吸”。虎门大桥是广东沿海地区重要的交通枢纽&#xff0c;始建于1992年&#xff0c;1997年通车至今&#xff0c;大桥一直都十分平稳。但在5月5日下午&#xff0c;虎门大桥发生异常抖动…

宝塔安装sqlserver_linux宝塔面板安装安装 pdo_sqlsrv扩展

第一步安装源curl https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssqlrelease.repo第二步安装驱动yum install msodbcsql mssql-tools unixODBC-devel第三步下载pdo-sqlsrv源码wget http://pecl.php.net/get/pdo_sqlsrv-5.6.1.tgztar -zxvf …

【java8新特性】——方法引用(四)

一、简介 方法引用是java8的新特性之一&#xff0c; 可以直接引用已有Java类或对象的方法或构造器。方法引用与lambda表达式结合使用&#xff0c;可以进一步简化代码。 来看一段简单代码&#xff1a; public static void main(String[] args) {List<String> strList Ar…

MySQL 排名函数.md

概述 MySQL 自带的排名的函数&#xff0c;主要有&#xff1a; row_number()rank()dense_rank()ntile() 测试数据 测试数据如下所示&#xff1a; row_number() 函数 用法如下&#xff1a; SELECT row_number() OVER (ORDER BY Salary DESC) row_num,Salary FROMEmployee查…

【java8新特性】——默认方法(五)

一、简介 默认方法是指接口的默认方法&#xff0c;它是java8的新特性之一。顾名思义&#xff0c;默认方法就是接口提供一个默认实现&#xff0c;且不强制实现类去覆写的方法。默认方法用default关键字来修饰。 默认方法可以解决的痛点&#xff1a; 在java8之前&#xff0c;修…

Java 序列化总结.md

概述 序列化&#xff1a;将对象写入到 IO 流中反序列化&#xff1a;从 IO 流中恢复对象 实现方法 实现 Serializable 或者 Externalizable Serializable&#xff1a;标记接口&#xff0c;不用实现任何方法&#xff0c;可以指定序列化 IDExternalizable&#xff1a;增强的序…

多线程买票案例

测试类 package thead;public class testThread {public static void main(String [] arg){Tickets ticket new Tickets();Thread t1 new Thread(ticket,"窗口一&#xff1a;");Thread t2 new Thread(ticket,"窗口二&#xff1a;");Thread t3 new Thr…

深度学习auc_机器学习集成学习与模型融合!

↑↑↑关注后"星标"Datawhale每日干货 & 每月组队学习&#xff0c;不错过Datawhale干货 作者&#xff1a;李祖贤&#xff0c;深圳大学&#xff0c;Datawhale高校群成员对比过kaggle比赛上面的top10的模型&#xff0c;除了深度学习以外的模型基本上都是集成学习的…

常用并发工具类(锁和线程间通信工具类)

常用并发工具类总结 JUC 下的常用并发工具类&#xff08;锁和线程间通信工具类&#xff09;&#xff0c;主要包括 ReentrantLock、ReentrantReadWriteLock、CountDownLatch、CyclicBarrier、Semaphore、Exchanger ReentrantLock 和 ReentrantReadWriteLock ReentrantLock 是…

of方法:给集合一次性添加多个元素

of()方法只是Map&#xff0c;List&#xff0c;Set这三个接口的静态方法&#xff0c;其父类接口和子类实现并没有这类方法&#xff0c;比如 HashSet&#xff0c;ArrayList返回的集合是不可变的&#xff0c;再次添加会报错Set与Map集合不可以存储重复的元素&#xff0c;否则会报错…

数控车椭圆编程实例带图_数控车床编程教程,图文实例详解

一、数控车编程特点(1) 可以采用绝对值编程(用X、Z表示)、增量值编程(用U、W表示)或者二者混合编程。(2) 直径方向(X方向) 系统默认为直径编程&#xff0c;也可以采用半径编程&#xff0c;但必须更改系统设定。(3) X向的脉冲当量应取Z向的一半。(4)采用固定循环&#xff0c;简化…

常用并发工具类(并发集合类)

文章目录概述BlockingQueueArrayBlockingQueue数据存储相关属性阻塞特性相关属性主要方法LinkedBlockingQueueLinkedBlockingQueue 主要属性LinkedBlockingQueue 设计思想ConcurrentLinkedQueuePriorityBlockingQueuePriorityBlockingQueue 主要属性PriorityBlockingQueue 设计…

参考文献起止页码怎么写_毕业论文文献综述不会写?快来看看这篇文章(附含通用模板)...

文献综述是对所研究主题的现状进行客观的叙述和评论、寻求新的研究突破点。一个资料全面、研究深入的综述不仅可以帮助作者确立毕业论文的选题&#xff0c;还可以为论文的深入研究提供有力的支撑。本文分享一份"毕业论文文献综述万能模板",以供参考。一、文献综述的基…

常用并发工具类(线程池)

文章目录概述ThreadPoolExecutorThreadPoolExecutor 的主要属性Worker 主要属性线程池的状态线程池的状态流转线程池提交任务的执行流程线程数量的设置线程池的种类FixedThreadPoolCachedThreadPoolSingleThreadExecutorScheduledThreadPoolExecutorSingleThreadScheduledExecu…

【Java 8 新特性】Java Stream.of()用法示例

本页将介绍Java Stream.of方法示例。Stream.of用于为给定元素创建顺序流。我们可以传递单个元素或多个元素。 查看javadoc中Stream.of方法声明。 static <T> Stream<T> of(T t) 参数&#xff1a;传递单个元素。 返回&#xff1a;该方法返回一个包含一个元素的流。…

Java 类加载机制

文章目录概述类的生命周期类加载的时机类加载的主要 5 个阶段加载验证准备准备阶段初始值的含义解析符号引用直接引用解析阶段的理解静态绑定与动态绑定初始化类加载器类加载器与类之间的关系类加载器的种类双亲委派机制双亲委派机制设计目的破坏双亲委派机制破坏双亲委派机制的…