前言:
java8中有两大最为重要的改变。第一个是Lambda 表达式;另外一个则是Stream API(java.util.stream.*)。Stream 是Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询。也可以使用Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。如果你之前接触过scala语言及spark,或是其他的大数据计算引擎,那么理解java流式编程相容易,思路相同,但是java流程编程还不够完善,处理并行计算远没有spark那么成熟,如果你没接触过可能对流程编程理解相对困难一些。
目录
一、什么是Stream
二、Stream 操作三步骤
2.1、创建Stream
2.1.1、Java8 中的Collection 接口被扩展,提供了两个获取流的方法:
2.1.2、由数组创建流
2.1.3、由值创建流
2.1.4、由函数创建流:创建无限流可以使用静态方法Stream.iterate() 和Stream.generate(), 创建无限流。
2.2、Stream 的中间操作
2.3、Stream 的终止操作
三、并行流与串行流
四、小结
一、什么是Stream
Stream是数据渠道,用于操作数据源(集合,数组,文件,正则表达式模式匹配器,伪随机数生成器和其他流等)所生成的元素序列,流中的数据元素可以是对象引用或基本类型。支持三种基本类型:int,long和double。并且有串行、并行两种执行模式,并行模式充分的利用了多核处理器的优势,使用fork/join框架进行了任务拆分,同时提高了执行速度。
对fork/join框架有兴趣的请阅读
集合是存储数据,而流是计算集合中的数据。
- 特点:
- Stream自己不会存储元素。
- Stream的操作不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。它会等到需要结果的时候才执行。也就是执行终端操作的时候。
二、Stream 操作三步骤
- 创建Stream
一个数据源(如:集合、数组),获取一个流
- 中间操作
一个中间操作链,对数据源的数据进行处理
- 终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果
2.1、创建Stream
2.1.1、Java8 中的Collection 接口被扩展,提供了两个获取流的方法:
- default Stream<E> stream() : 返回一个顺序流;
- default Stream<E> parallelStream() : 返回一个并行流;
2.1.2、由数组创建流
Java8 中的Arrays 的静态方法stream() 可以获取数组流:
static <T> Stream<T> stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
- public static IntStream stream(int[] array)
- public static LongStream stream(long[] array)
- public static DoubleStream stream(double[] array)
2.1.3、由值创建流
可以使用静态方法Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。
public static<T> Stream<T> of(T... values) : 返回一个流
2.1.4、由函数创建流:创建无限流可以使用静态方法Stream.iterate() 和Stream.generate(), 创建无限流。
- 迭代:public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
- 生成:public static<T> Stream<T> generate(Supplier<T> s) :
2.2、Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
类别 | 方法 | 描述 |
筛选与切片 | filter(Predicatep) | 接收Lambda ,从流中排除某些元素。 |
distinct() | 筛选,通过流所生成元素的hashCode 除重复元素 | |
limit(long maxSize) | 截断流,使其元素不超过给定数量。 | |
skip(long n) | 跳过元素,返回一个扔掉了前n 个元素的流。若流中元素 不足n 个,则返回一个空流。与limit(n) 互补 | |
映射 | map(Functionf) | 接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每素上,产生一个新的DoubleStream。 | |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。 | |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。 | |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 | |
排序 | sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparatorcomp) | 产生一个新流,其中按比较器顺序排序 |
2.3、Stream 的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void 。
类别 | 方法 | 描述 |
查找与匹配 | allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 | |
noneMatch(Predicatep) | 检查是否没有匹配所有元素 | |
findFirst() | 返回第一个元素 | |
findAny() | 返回当前流中的任意元素 | |
count() | 返回流中元素总数 | |
max(Comparatorc) | 返回流中最大值 | |
min(Comparatorc) | 返回流中最小值 | |
forEach(Consumerc) | 内部迭代(使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了) | |
归约 | reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来得到一个值。 返回T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。 返回Optional<T> | |
收集 | collect(Collector c) | 将流转换为其他形式。接收一个Collector接口的 实现,用于给Stream中元素做汇总的方法,Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例 |
| toList | 把流中元素收集到List 。返回List<T> |
List<Employee>emps=list.stream().collect(Collectors.toList()); | ||
| toSet | 把流中元素收集到Set。返回Set<T> |
| Set<Employee>emps=list.stream().collect(Collectors.toSet()); | |
| toCollection | 把流中元素收集到创建的集合。返回Collection<T> |
| Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new)); | |
| counting | 计算流中元素的个数。返回 Long |
| long count=list.stream().collect(Collectors.counting()); | |
| summingInt | 对流中元素的整数属性求和。返回 nteger |
| int total=list.stream().collect(Collectors.summingInt(Employee::getSalary)); | |
| averagingInt | 计算流中元素Integer属性的平均值。返回Double |
| double avg=list.stream().collect(Collectors.averagingInt(Employee::getSalary)); | |
| summarizingInt | 收集流中Integer属性的统计值。如:平均值。 返回类型IntSummaryStatistics |
| IntSummaryStatisticsiss=list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); | |
| joining | 连接流中每个字符串,返回String |
| Stringstr=list.stream().map(Employee::getName).collect(Collectors.joining()); | |
| maxBy | 根据比较器选择最大值 。返回Optional<T> |
| Optional<Emp>max=list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary))); | |
| minBy | 根据比较器选择最小值。返回Optional<T> |
| Optional<Emp>min=list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary))); | |
| reducing | 从一个作为累加器的初始值开始,利用BinaryOperator与 流中元素逐个结合,从而归约成单个值。返回归约产生的类型 |
| int total=list.stream().collect(Collectors.reducing(0,Employee::getSalar,Integer::sum)); | |
| collectingAndThen | 包裹另一个收集器,对其结果转换函数。返回转换函数返回的类型 |
| int how=list.stream().collect(Collectors.collectingAndThen(Collectors.toList(),List::size)); | |
| groupingBy | 根据某属性值对流分组,属性为K,结果为V。返回Map<K,List<T>> |
| Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus)); | |
| partitioningBy | 根据true或false进行分区 。返回Map<Boolean,List<T>> |
| Map<Boolean,List<Emp>>vd=list.stream().collect(Collectors.partitioningBy(Employee::getManage)); |
三、并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel() 与sequential() 在并行流与顺序流之间进行切换。
四、小结
- 有些任务最好使用流来完成,有些任务最好使用迭代来完成。将这两种方法结合起来,可以最好地完成许多任务。对于选择使用哪种方法进行任务,没有硬性规定,但是有一些有用的启发式方法。在许多情况下,使用哪种方法将是清楚的;在某些情况下,则不会很清楚。如果不确定一个任务是通过流还是迭代更好地完成,那么尝试这两种方法,看看哪一种效果更好。
- 编程流管道的本质是无副作用的函数对象。这适用于传递给流和相关对象的所有许多函数对象。终结操作forEach仅应用于报告流执行的计算结果,而不是用于执行计算。为了正确使用流,必须了解收集器。最重要的收集器工厂是toList,toSet,toMap,groupingBy和join。
- 在编写返回元素序列的方法时,请记住,某些用户可能希望将它们作为流处理,而其他用户可能希望迭代方式来处理它们。尽量适应两个群体。如果返回集合是可行的,请执行此操作。如果已经拥有集合中的元素,或者序列中的元素数量足够小,可以创建一个新的元素,那么返回一个标准集合,比如ArrayList。否则,请考虑实现自定义集合,就像我们为幂集程序里所做的那样。如果返回集合是不可行的,则返回流或可迭代的,无论哪个看起来更自然。如果在将来的Java版本中,Stream接口声明被修改为继承Iterable,那么应该随意返回流,因为它们将允许流和迭代处理。
- 甚至不要尝试并行化流管道,除非你有充分的理由相信它将保持计算的正确性并提高其速度。不恰当地并行化流的代价可能是程序失败或性能灾难。如果您认为并行性是合理的,那么请确保您的代码在并行运行时保持正确,并在实际情况下进行仔细的性能度量。如果您的代码是正确的,并且这些实验证实了您对性能提高的怀疑,那么并且只有这样才能在生产代码中并行化流。