强大的 Stream API
Java8 中有两大最为重要的改变。第一个是 Lambda 表达式,另外一个是 Stream API (java.util.stream.*)
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来执行并行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
流(Stream) 到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
一句话来讲:集合讲的是数据,流讲的是计算!
注意:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream 的操作分为三个步骤
- 创建 Stream:一个数据源(如:集合,数组),获取一个流
- 中间操作:一个中间操作链,对数据源的数据进行处理
- 终止操作:一个终止操作,执行中间操作链,并产生结果
特别注意:中间操作是不会执行任何实际操作的,它相当于声明一个操作内容,只有执行终止操作的时候,才会执行中间操作链
一、创建 Stream
-
可以通过 Collection 系列集合提供的方法创建:
stream()
或者parallelStream()
-
可以通过 Arrays 中的静态方法
stream()
获取数组流 -
可以通过 Stream 类中的静态方法
of()
@Test public void createStream(){ Stream<String> stream = Stream.of("aa", "bb", "cc"); }
-
可以创建无限流
迭代和生成,感觉没啥用,需要配合limit使用
迭代:
Stream.iterate(0, x -> x + 2);
生成:
Stream.generate(0, x -> x + 2);
二、中间操作——筛选和切片
filter——参数是一个 Predicate 的函数式接口,boolean test(T t);
,从流中排除某些元素
limit——截断流,使其元素不超过给定数量
skip——跳过元素,返回一个人掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit相反
distinct——筛选,通过流所生成元素的 hashCode()
和 equals()
去除重复元素
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为“惰性求值”。
三、中间操作——映射
map——接受一个Function<T, R>的函数型接口 R apply(T t);
,将元素转换成其他形式或提取信息。接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap——接受一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连成一个流。
四、中间操作——排序
sorted()——自然排序(Comparable)
sorted(Comparator)——定制排序
五、终止操作——查找与匹配
allMatch——检查是否匹配所有元素
anyMatch——检查是否至少匹配一个元素
noneMatch——检查是否没有匹配所有元素
findFirst——返回第一个元素
findAny——返回当前流中的任意元素
count——返回流中元素的总个数
max——返回流中最大值
min——返回流中最小值
六、终止操作——归纳与收集
归约:reduce (T identity, BinaryOperator) / reduce(BinaryOperator)
——可以将流中的元素反复结合起来,得到一个值。
map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名
收集:collect(Collector collector)
将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法。参数Collector是一个接口,接口中方法的实现决定了如何对流执行收集操作(如:收集到List,Set,Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
方法 | 作用 |
---|---|
Collectors.toList() | 转成List |
Collectors.toSet() | 转成Set |
Collectors.toCollection(Supplier s) | 转换成想要的集合,如:ArrayList,HashSet |
Collectors.toMap() | 转换成Map |
Collectors.counting() | 获取集合的总数 |
Collectors.averagingInt(ToIntFunction<T> i) Collectors.averagingDouble(ToDoubleFunction<T> d) Collectors.averagingLong(ToLongFunction<T> l) | 获取平均值 |
Collectors.summingInt(ToIntFunction<T> i) Collectors.summingDouble(ToDoubleFunction<T> d) Collectors.summingLong(ToLongFunction<T> l) | 获取总和 |
Collectors.maxBy() | 获取最大值 |
Collectors.minBy() | 获取最小值 |
Collectors.groupingBy(Function<T, K> classifier) | 按照某个属性进行分组 |
Collectors.groupingBy(Function<T, K> classifier, Collector<T, A, D> downstream | 多级分组 |
partitioningBy(Predicate<T> predicate) | 分区,以boolean值为键的map |
Collectors.joining() Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) | 连接操作,如果是自定义的实体类型,需要配合map()使用 |
几个实例:
Collectors.joining()
Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
@Test
public void testJoin() {
List<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
String joinString = list.stream().collect(Collectors.joining(","));
//String类型的集合,可以使用 String.join(",", list); 效果是一样的。
String joinString2 = list.stream().collect(Collectors.joining("', '", "'","'"));
System.out.println(joinString);
System.out.println(joinString2);
}
输出结果:
aa,bb,cc
‘aa’, ‘bb’, ‘cc’
并行流与串行流
并行流就是把一个内容分割成多个数据块,并用不同的线程分别处理每个数据块的流。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性的通过parallel()
与sequential()
在并行流与顺序流之间进行切换。
了解 Fork/Join 框架
**Fork/Join 框架:**就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),
再将一个个小任务运算的结果进行 join 汇总。
Fork/Join 框架与传统线程池的区别
采用“工作窃取”模式(work-stealing):
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能。
接口中的默认方法
接口默认方法的“类优先”原则
若接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
- 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否为默认方法),那么必须覆盖该方法来解决冲突。
通俗来讲就是优先选择 抽象类中的方法,但是如果都是接口方法,那么需要指定接口中的方法来实现