高中的时候我们就学过电流,水流,磁场流,然后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接口
public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable {}这是一个泛型接口,T指定流中元素的类型,S指定扩展BaseStream的流的类型。BaseStream类extends了AutoCloseable接口,所以可以使用带资源的try语句管理流。在大多数的情况下,比如数据源是集合的时候,都不需要关闭流的。
这个类基本实际编码中基本不会用到,里面的几个方法也都是些属性状态判断的方法,所以了解下就好了,这里不列出具体的API了。我们还要主要研究下Stream接口,几个基本类型的接口我们最后整理。
- 流接口--Stream接口
public interface Stream<T> extends BaseStream<T, Stream<T>> {}这个类里面好多方法我们要认真的了解下的,我们学习流API其实也就是学习这几个方法而已,在详细的整理的API之前,先来说3组概念。
- 1,终端操作VS中间操作
中间操作会产生另外一个流,所以,中间操作可以用来创建执行一系列动作的管道。
- 2,延迟行为
- 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,一个简单的流示例
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("这里随便一个异常")); }