在JDK8新增的许多功能中,有2个功能最重要,一个是Lambda表达式,一个是流API。Lambda表达式前面我已经整理过了,现在开始整理流API。首先应该如何定义流API中的“流”这个概念呢?记住:流是数据的渠道。



高中的时候我们就学过电流,水流,磁场流,然后java中还有io流,那么这个流到底是什么东西呢?我们这样子来理解:流是一种对抽象化事物的模拟。流本身并不存储数据,它只是代表一个对象序列,流操作本身并不修改数据源。出于某些原因呢,我们不好研究数据源本身,所以我们就用流来模拟一个对象序列,在这里流里面做一些数据过滤,排序和其他的操作,它只是移动数据,或者说压根就不是客观存在的东西,所以流操作本身不会修改数据源(这点也是我们需要特别注意的)。



现在我们较为直观的来看一段代码来初体验下流,假设我们现在有这么一个情景,我们需要统计一个Integer类型的集合中元素大于2的数量,按照以前我们的写法我们来编码:

public static void main(String[] args)
{
//模拟一个集合和一个累加器
List<Integer> list = Lists.newArrayList(1, 2, 3);
int count = 0;
//然后迭代该集合
for (Integer integer : list)
{
if (integer > 2)
{
count++;
}
}
System.out.println("集合中元素的个数:" + count);
}

上面的代码我们以前经常写,有什么问题吗?没有。只不过它很难被并行运算。这也是java8引入大量操作符的原因。我们现在使用java8中的流来实现上面的情景:

public static void main(String[] args)
{
//模拟一个集合和一个累加器
List<Integer> list = Lists.newArrayList(1, 2, 3);
//使用java8的流,编码量也大大的减小了
System.out.println(list.stream().filter(value -> value > 2).count());
}

我们来对比下上面的2段代码,一个Stream表面上看与一个集合很类似,允许你改变和获取数据。但是实际上它与集合还是有很大区别的:

1,Stream不会存储元素。元素可能被存储在底层的集合中,或者根据需要产生出来

2,Stream操作符不会改变源对象,相反,他们会返回一个持有结果的新的Stream

public static void main(String[] args)
{
//模拟一个集合
List<Integer> list = Lists.newArrayList(1, 2, 3);
long count = list.stream().filter(value -> value >= 2).count();
System.out.println("使用这个流来做一些操作:该流的数量是" + count);
//一下代码我们输入原来的数据源看下,原来的数据源是否发生了改变。事件证明,原来的数据源不变
for (Integer integer : list)
{
System.out.println(integer);
}
}

3,Stream可能是延迟执行的,也就是说他们会等到需要结果的时候才执行。

4,我们现在已经发现了,使用Steam表达式比循环的可读性更好。其实还有最重要的一点就是他们很容易被并行执行。

System.out.println(list.parallelStream().filter(value -> value > 2).count());

总结:我们应该深深体会到Stream设计的原则--做什么,而不是怎么去做

当你使用Stream时,你会通过三个阶段来建立一个操作流水线

1,创建一个Stream

2,在一个或者多个步骤中,指定将初始的Stream转换为另外一个Stream的中间操作

3,使用一个终端操作来产生一个结果,该操作会强制它之前的延迟操作立即执行,在这之后,该Stream就不会再被使用了。

  • 流接口--BaseStream接口

流API定义了几个流接口,这些接口包含在java.util.stream中。BaseStream是基础接口,它定义了所有流都可以使用的基本功能。我们来看一下源码:

public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable {}

这是一个泛型接口,T指定流中元素的类型,S指定扩展BaseStream的流的类型。BaseStream类extends了AutoCloseable接口,所以可以使用带资源的try语句管理流。在大多数的情况下,比如数据源是集合的时候,都不需要关闭流的。

这个类基本实际编码中基本不会用到,里面的几个方法也都是些属性状态判断的方法,所以了解下就好了,这里不列出具体的API了。我们还要主要研究下Stream接口,几个基本类型的接口我们最后整理。

  • 流接口--Stream接口

Stream也是一个接口,ReferencePipeline是它具体的实现类。具体的实现类,我们不用关心,因为一般都是直接使用这个接口的API就好了。Stream接口的定义如下:

public interface Stream<T> extends BaseStream<T, Stream<T>> {}

这个类里面好多方法我们要认真的了解下的,我们学习流API其实也就是学习这几个方法而已,在详细的整理的API之前,先来说3组概念。

  • 1,终端操作VS中间操作

终端操作会消费流,这种操作作用于产生结果,例如找出流中最小的值,或者执行某种操作,比如forEach方法。一个流被消费后,就不能被重用了。

中间操作会产生另外一个流,所以,中间操作可以用来创建执行一系列动作的管道。

  • 2,延迟行为

延迟机制就是说某一种操作不是立即发生的。可能在中间的某一个时刻才会发生。上面的中间操作就不是立即发生的。当在中间操作创建的新流上执行终端操作后,中间操作指定的操作才会发生。之所有有这种机制,应该是出于性能原因吧,延迟行为可以让流API更加高效的执行

  • 3,有状态VS无状态

无状态操作就是说,独立于其他元素来处理每一个元素。在有状态的操作中,每个元素的处理可能依赖于其他的元素。举个例子吧:比如说排序就应该是有状态操作,因为元素的顺序依赖于其他的元素的值。比如说过滤就应该是无状态操作,因为每个元素都是被单独处理的。当需要并行处理流的时候,无状态和有状态的区别最为重要,因为有状态操作可能需要多次处理才能完成。





其实学习流API挺简单的,就是获取一个流,然后调相关的流API来操作就OK了。

  • 1,如何获取流?

想要获取一个流,肯定要有一个数据源。

1),如果数据源是集合的话,有2个方法可以获得一个流,下面是Collections的2个获取流的源码:

//该方法默认返回一个顺序流
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
} //该方法默认返回一个并行流,如果无法获得一个并行流,也有可能返回一个顺序流
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}

2),如果数据源是数组的话,用Arrays工具类的一个stream()静态方法,

public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}

该方法还有几个重载方法,用来返回处理基本类型的数组,他们返回的类型有IntStream,DoubleStream,LongStream。

3),通过对一个流做中间操作来获取一个新的流。

4),创建包含指定元素集合的流,使用of()方法。

下面是获取流的一段代码:

/**
* @创建作者: LinkinPark
* @创建时间: 2015年11月3日
* @功能描述: 获取流
*/
public class Test
{ public static void main(String[] args)
{
//1,数据源是集合,从集合中获取一个流
List<Integer> list = new ArrayList<>(3);
list.add(1);
list.add(2);
list.add(3);
Stream<Integer> stream1 = list.stream(); //2,数据源是一个数组,从数组中获取一个流
Integer[] array = list.toArray(new Integer[0]);
Stream<Integer> stream2 = Arrays.stream(array); //3,使用原来的一个流来生成一个新的流
Stream<Integer> stream3 = stream1.filter((i) -> true); //4,直接使用Stream接口的静态方法of
Stream<Integer> stream4 = Stream.of(1, 2, 3);
stream4.forEach(System.out::println);
} }
  • 2,OK,现在我来整理下具体的流的API:

先来整理中间操作的方法:

filter(),过滤掉Steam中所有不符合predicate接口的元素

mapToXxx(),对流中的元素执行一对一的转换,该方法返回的新流中包含了ToXxxFunction接口的元素

peek(),依次对每个元素执行一些操作,该方法返回的流与原来流包含相同的元素,该方法主要用于调试。

distinct(),排序流中所有重复的元素,注意,这里元素重复的标准是使用equals()返回true。该方法有状态

sorted(),保证流中的元素在后续的访问中出于有序状态。该方法有状态

limit(),用于保证对流的后续访问中最大允许访问的元素个数。该方法有状态





整理几个终端操作的方法:

forEach(),遍历流中的所有元素,对每个元素执行Consumer接口

toArray(),将流中所有的元素转换成一个数组

reduce(),用于通过某种操作来合并流中的元素

min(),返回流中所有元素的最小值

max(),返回流中所有元素的最大值

count(),返回流中所有元素的数量

anyMatch(),判断流中是否至少包含一个元素符合Predicate接口

allMatch(),判断流中是否每个元素都符合Predicate接口

noneMatch(),判断流中是否所有元素都不符合Predicate接口

findFirst(),返回流中的第一个元素

findAny(),返回流中的任意一个元素

  • 3,一个简单的流示例

OK,现在通过一段代码来做一个简单的流示例:

public static void main(String[] args) throws Exception
{
//这里初始化一个list,下面Stream所有的操作都不会影响这个数据源的
List<Integer> list = new ArrayList<>(3);
list.add(1);
list.add(2);
list.add(3);
//获取一个流,来演示下min取最小值的操作
Stream<Integer> stream = list.stream();
Optional<Integer> min = stream.min(Integer::compare);
min.ifPresent(System.out::println);
//上面的min是终端操作,所以流被消费了。下面演示下链式操作,这也是Optional类和Stream流推荐的方式
list.stream().filter((value) -> value < 2).forEach(System.out::println);
list.stream().max(Integer::compare).filter((value) -> value > 5).orElseThrow(() -> new Exception("这里随便一个异常"));
}

流API--初体验的更多相关文章

  1. Hbase王国游记之:Hbase客户端API初体验

    §历史回顾 2018年岁末,李大胖朦胧中上了开往Hbase王国的车,伴着一声长鸣,列出缓缓驶出站台,奔向无垠的广袤. (图片来自于网络) 如不熟悉剧情的,可观看文章: 五分钟轻松了解Hbase列式存储 ...

  2. Kong Api 初体验

    请查看原文: https://www.fangzhipeng.com/nginx/kong/2016/07/11/kong-api-gateway/ Kong是一个可扩展的开源API层(也称为API网 ...

  3. web api 初体验之 GET和POST传参

    上一篇我们讲到了web api跨域的问题 它几乎是每一个用web api的人都需要去解决的问题,不然都没法测试.接下来会遇到的问题就是传参了.还是用js前台调用服务的方式. GET 方式 get方式传 ...

  4. web api 初体验 解决js调用跨域问题

    跨域界定 常见跨域: 同IP不同端口: http:IP:8001/api/user     http:IP:8002/api/user 不同IP不同端口: http://172.28.20.100:8 ...

  5. Java高级特性1_流库_初体验

    Java高级特性流库_初体验 面对结果编程 在编程里, 有两种编程方式, 一种是面对过程编程, 一种是面对结果编程. 两者区别如下 面向过程编程 面向过程编程需要编程程序让程序依次执行得到自己想要的结 ...

  6. # 初体验之腾讯位置服务彩云天气开发者api

    初体验 最近接触到了boxjs,看到了里面一个比较有意思的彩云天气的脚本,由于自己本身就是彩云天气pro的用户,日常使用过程中感觉到彩云的降雨提醒还是挺方便的,于是就准备开始使用这个天气的脚本. 脚本 ...

  7. node.js 初体验

    node.js 初体验 2011-10-31 22:56 by 聂微东, 174545 阅读, 118 评论, 收藏, 编辑 PS: ~ 此篇文章的进阶内容在为<Nodejs初阶之express ...

  8. ASP.NET Core 3.0 上的gRPC服务模板初体验(多图)

    早就听说ASP.NET Core 3.0中引入了gRPC的服务模板,正好趁着家里电脑刚做了新系统,然后装了VS2019的功夫来体验一把.同时记录体验的过程.如果你也想按照本文的步骤体验的话,那你得先安 ...

  9. 【Spark深入学习 -15】Spark Streaming前奏-Kafka初体验

    ----本节内容------- 1.Kafka基础概念 1.1 出世背景 1.2 基本原理 1.2.1.前置知识 1.2.2.架构和原理 1.2.3.基本概念 1.2.4.kafka特点 2.Kafk ...

  10. Java8初体验(二)Stream语法详解---符合人的思维模式,数据源--》stream-->干什么事(具体怎么做,就交给Stream)--》聚合

    Function.identity()是什么? // 将Stream转换成容器或Map Stream<String> stream = Stream.of("I", & ...

随机推荐

  1. Flask知识点一

    1 flask安装 pip3 install falsk 一Werkzeug Werkzeug是什么? Werkzeug就是Python对WSGI的实现的一个通用库,它是Flask所使用的底层WSGI ...

  2. 表单验证控件Verify.js

    自己工作常用到表单录入验证,就顺手写了一个验证控件,刚开始写得很烂.多年后翻出来,又优化了一下,增加了一些功能.拿出来分享分享. 主要功能就是表单的录入验证. * 1.当录入框必填时,在控件后生成红色 ...

  3. PE文件详解(八)

    本文转载自小甲鱼PE文件详解系列教程原文传送门 当应用程序需要调用DLL中的函数时,会由系统将DLL中的函数映射到程序的虚拟内存中,dll中本身没有自己的栈,它是借用的应用程序的栈,这样当dll中出现 ...

  4. [整理]HTTPS和SSL证书

    在互联网安全通信方式上,目前用的最多的就是https配合ssl和数字证书来保证传输和认证安全了.本文追本溯源围绕这个模式谈一谈. 名词解释 首先解释一下上面的几个名词: • https:在http(超 ...

  5. PC端截取GIF图片的软件

    PC端截取GIF图片的软件分享:下载>>

  6. jdk1.8新特性 : 接口中可以有普通方法(非静态方法)和静态方法 , 颠覆了之前我的理解 : 接口中只能有共有常量和抽象方法的概念,后面必须要加一句jdk1.7和1..7之前

    看到jdk某些接口中存在default方法,于是... http://shaomeng95.iteye.com/blog/998820    为什么接口只能是公有常量? public interfac ...

  7. VS工程中添加c/c++工程中外部头文件及库的基本步骤

    转载自 在VS工程中,添加c/c++工程中外部头文件及库的基本步骤: 1.添加工程的头文件目录:工程---属性---配置属性---c/c++---常规---附加包含目录:加上头文件存放目录. 2.添加 ...

  8. cs231n spring 2017 lecture3 Loss Functions and Optimization 听课笔记

    1. Loss function是用来量化评估当前预测的好坏,loss function越小表明预测越好. 几种典型的loss function: 1)Multiclass SVM loss:一般的S ...

  9. CTF---Web入门第十题 Once More

    Once More分值:10 来源: iFurySt 难度:易 参与人数:4782人 Get Flag:2123人 答题人数:2166人 解题通过率:98% 啊拉?又是php审计.已经想吐了. hin ...

  10. 【Java学习笔记之十七】Java中普通代码块,构造代码块,静态代码块区别及代码示例分析

    //执行顺序:(优先级从高到低.)静态代码块>mian方法>构造代码块>构造方法. 其中静态代码块只执行一次.构造代码块在每次创建对象是都会执行. 1 普通代码块 //普通代码块:在 ...