1、Stream流
得益于Lambda所带来的函数式编程,引入一个全新的Stream概念。用于解决已有集合类库既有的弊端。
集合循环遍历的弊端
集合都支持遍历操作,Java8中Lambda让我们更专注于做什么,而不是怎么做。在之前的循环遍历中,可以发现:
- for循环的语法就是怎么做
- for循环的循环体才是做什么
使用循环的目的是遍历,遍历是指每一个每一个元素逐一进行处理,而不是从第一个到最后一个顺次处理的循环,遍历是目的,循环是方式。
试想一下,如果希望对集合中的元素进行筛选过滤:
- 将集合A根据条件过滤为子集合B。
- 然后将子集合B按照另外的条件过滤为子集合C。
- 接下来还有循环遍历去使用。
每次对元素进行操作的时候只能进行遍历、再遍历,因为线性循环只能遍历一次,这种方式很不爽。Lambda的衍生物Stream能给我们带来更优雅的写法
public class Demo1StreamFilter{
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("张三疯");
list.stream().filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
直接阅读代码的字面意思就可以完美展示逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。这种方式并没有体现使用线性循环或者是其他任何算法进行遍历,我们真正要做的事情内容被更好的体现在代码当中。
1.1 流思想的概述
当需要对多个元素进行操作的时候,考虑到性能及便利性,我们应该首先拼好一个模型不走方案,然后再按照方案去执行它。
Stream是一个来自数据源的元素队列。元素是特定类型的对象,形成一个队列,java中的Stream并不会存储元素,而是按需计算。 数据源是流的来源,可以是集合,数组等。
整体来看流式思想类似于工厂车间的 生产流水线当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个 模型 步骤方案,然后再按照方案去执行她。这张图中展示了过滤、映射、跳过、计数等操作,这是一种集合元素的处理方案,就是一种函数模型,图中每一方框都是一个流,调用指定的方法,可以从一个流模型转换为另一个流模型,最右侧的数值3就是最终结果。
这里的filter、map、skip都是对函数模型进行操作,集合元素并没有真正被处理,只有在终结方法count执行的时候,整个模型才会按照指定的策略进行操作,这得益于Lambda的延时执行特性。
流其实是一个元素的函数模型,它并不是集合、也不是数据结构,其本身并不存储元素。
1.2 stream内部迭代和外部迭代
一般来说,我们之前的编码方法,叫外部迭代,stream的写法叫内部迭代。内部迭代代码更加可读更加优雅,关注点是做什么(外部迭代关注是怎么样做),也很容易让我们养成编程小函数的好习惯!这点在编程习惯里面非常重要!看例子:
import java.util.stream.IntStream;
public class StreamDemo1 {
public static void main(String[] args) {
int[] nums = { 1, 2, 3 };
// 外部迭代
int sum = 0;
for (int i : nums) {
sum += i;
}
System.out.println("结果为:" + sum);
// 使用stream的内部迭代
// map就是中间操作(返回stream的操作)
// sum就是终止操作
int sum2 = IntStream.of(nums).map(StreamDemo1::doubleNum).sum();
System.out.println("结果为:" + sum2);
System.out.println("惰性求值就是终止没有调用的情况下,中间操作不会执行");
IntStream.of(nums).map(StreamDemo1::doubleNum);
}
public static int doubleNum(int i) {
System.out.println("执行了乘以2");
return i * 2;
}
}
1.3 stream操作类型
当使用一个流的时候,通常包括三个步骤:获取一个数据源—>数据转换—>执行操作获取想要的结果,每次转换原有的Stream对象不改变,返回新的Stream对象(可以多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
操作类型概念要理清楚。有几个维度。首先分为 中间操作 和 最终操作,在最终操作没有调用的情况下,所有的中级操作都不会执行。那么那些是中间操作那些是最终操作呢?
简单来说,返回stream流的就是中间操作,可以继续链式调用下去,不是返回stream的就是最终操作。这点很好理解。最终操作里面分为短路操作和非短路操作,短路操作就是limit/findxxx/xxxMatch这种,就是找了符合条件的就终止,其他的就是非短路操作。在无限流里面需要调用短路操作,否则像炫迈口香糖一样根本停不下来!
中间操作又分为 有状态操作 和 无状态操作,怎么样区分呢?
一开始很多同学需要死记硬背,其实你主要掌握了状态这个关键字就不需要死记硬背。状态就是和其他数据有关系。我们可以看方法的参数,如果是一个参数的,就是无状态操作,因为只和自己有关,其他的就是有状态操作。如map/filter方法,只有一个参数就是自己,就是无状态操作;而distinct/sorted就是有状态操作,因为去重和排序都需要和其他数据比较,理解了这点,就不需要死记硬背了!为什么要知道有状态和无状态操作呢?在多个操作的时候,我们需要把无状态操作写在一起,有状态操作放到最后,这样效率会更加高。
1.4 获取流
java.util.stream.Stream是Java8新加入的流接口。 获取流非常简单
- 所有的Collection集合都可以通过stream默认方法获取流。 (例如:list.stream())
- Stream接口的静态方法of可以获取数组对应的流。 (例如:Stream.of(array))
1.5 常用方法
1.5.1 中间操作(延时方法)
返回值类型仍然是Stream接口自身类型的方法
1.5.1.1 过滤:filter
可以通过filter方法将一个流转换成另一个子集流
Stream filter(Predicate<? super T >predicate);
接收一个Predicte函数式接口参数作为筛选条件。
不知道查看上一个博客
public static void testFilter(){
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
//截取所有能被2整除得数据
List<Integer> collect = integers.stream().filter(i -> i % 2 == 0).collect(Collectors.toList());
System.out.println("collect = " + collect);
}
1.5.1.2 映射: map
如果需要将流中的元素映射到另一个流中,可以使用map方法。
Stream map(Function <? super T, ? extends R > map);
接收一个Function函数式接口参数作为参数。
public static void main(String[] args) {
//自己建好得一个获取对象list得方法
List<Dish> dishList = Dish.getDishList();
//获取每一道菜得名称 并放到一个list中
List<String> collect = dishList.stream().map(Dish::getName).collect(Collectors.toList());
//collect = [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
System.out.println("collect = " + collect);
}
1.5.1.3 distinct: 去重
该操作会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流。
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
List<Integer> collect = numbers.stream().distinct().collect(Collectors.toList());
System.out.println("collect = " + collect);
}
结果: collect = [1, 2, 3, 4]
1.5.1.4 sorted :排序
对流中得数据进行排序,可以以自然序或着用Comparator 接口定义的排序规则来排序一个流。Comparator
能使用lambada表达式来初始化,还能够逆序一个已经排序的流。
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(5, 8, 2, 6, 41, 11);
//排序默认为顺序 顺序 = [2, 5, 6, 8, 11, 41]
List<Integer> sorted = integers.stream().sorted().collect(Collectors.toList());
System.out.println("顺序 = " + sorted);
//逆序 逆序 = [41, 11, 8, 6, 5, 2]
List<Integer> reverseOrder = integers.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
System.out.println("逆序 = " + reverseOrder);
//也可以接收一个lambda
List<Integer> ages = integers.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
}
1.5.1.5 limit: 截取
该方法会返回一个不超过给定长度的流。
public static void testLimit(){
List<Integer> integers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
//截取流中得前三个元素 collect = [1, 2, 1]
List<Integer> collect = integers.stream().limit(3).collect(Collectors.toList());
System.out.println("collect = " + collect);
}
1.5.1.6 skip: 舍弃
该方法会返回一个扔掉了前面n个元素的流。如果流中元素不足n个,则返回一个空流。
public static void testSkip(){
List<Integer> integers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
//丢掉流中得前三个元素 collect = [3, 3, 2, 4]
List<Integer> collect = integers.stream().skip(3).collect(Collectors.toList());
System.out.println("collect = " + collect);
}
1.5.1.7 flatMap: 扁平化
该方法可以让你把一个流中的每个值都换成另一个流,然后把所有的流都链接起来成为一个流。 给 定 单 词 列表[“Hello”,“World”] ,你想要返回列表 [“H”,“e”,“l”, “o”,“W”,“r”,“d”],你可能会认为这很容易,通过map你可以把每个单词映射成一张字符表,然后调用 distinct来过滤重复的字符,但是这个方法的问题在于,传递给 map 方法的Lambda为每个单词返回了一个 String[] (String列表)。因此, map 返回的流实际上是Stream<String[]> 类型的。而你真正想要的是用Stream来表示一个字符流。 正确写法应该是通过flatMap对其扁平化并作出对应处理。
我的理解是假如你的集合流中包含子集合,那么使用flatMap可以返回该子集合的集合流.
public static void main(String[] args) {
String[] words = {"Hello", "World"};
List<String> collect = Stream.of(words). //数组转换流
map(w -> w.split("")). //去掉“”并获取到两个String[]
flatMap(Arrays::stream). //方法调用将两个String[]扁平化为一个stream
distinct(). //去重
collect(Collectors.toList());
//collect = [H, e, l, o, W, r, d]
System.out.println("collect = " + collect);
}
}
1.5.2 终止操作(终结方法)
循环 forEach
计算 min、max、count、average
匹配 anyMatch、allMatch、noneMatch、findFirst、findAny
汇聚 reduce
收集器 collect
1.5.2.1 collect:收集
从上面得代码已经可以看出来,collect是将最终stream中得数据收集起来,最终生成一个list,set,或者map。
public static void main(String[] args) {
List<Dish> dishList = Dish.getDishList();
//list
List<Dish> collect = dishList.stream().limit(2).collect(Collectors.toList());
//set
Set<Dish> collect1 = dishList.stream().limit(2).collect(Collectors.toSet());
//map
Map<String, Dish.Type> collect2 = dishList.stream().limit(2).collect(Collectors.toMap(Dish::getName, Dish::getType));
}
这里面生成map得toMap方法有三个重载,传入得参数都不同,这里使用得是传入两个Function类型得参数。当然,Collectors的功能还不止这些,下面的收集器中会有其他的详解。
1.5.2.2 anyMatch
anyMatch方法可以回答“流中是否有一个元素能匹配到给定的谓词”。会返回一个boolean值。
public class AnyMatch {
public static void main(String[] args) {
List<Dish> dish = Dish.getDish();
boolean b = dish.stream().anyMatch(Dish::isVegetarian);
System.out.println(b);
}
}
1.5.2.3 allMatch
allMatch方法和anyMatch类似,校验流中是否都能匹配到给定的谓词
class AllMatch{
public static void main(String[] args) {
List<Dish> dish = Dish.getDish();
//是否所有菜的热量都小于1000
boolean b = dish.stream().allMatch(d -> d.getCalories() < 1000);
System.out.println(b);
}
}
1.5.2.4 noneMatch
noneMatch方法可以确保流中没有任何元素与给定的谓词匹配。
class NoneMatch{
public static void main(String[] args) {
List<Dish> dish = Dish.getDish();
//没有任何菜的热量大于等于1000
boolean b = dish.stream().allMatch(d -> d.getCalories() >= 1000);
System.out.println(b);
}
}
anyMatch,noneMatch,allMatch这三个操作都用到了所谓的短路。
1.5.2.5 findAny
findAny方法将返回当前流中的任意元素。
class FindAny{
public static void main(String[] args) {
List<Dish> dish = Dish.getDish();
Optional<Dish> any = dish.stream().filter(Dish::isVegetarian).findAny();
System.out.println("any = " + any);
}
}
1.5.2.6 findFirst
findFirst方法能找到你想要的第一个元素。
class FindFirst{
public static void main(String[] args) {
List<Dish> dish = Dish.getDish();
Optional<Dish> any = dish.stream().filter(Dish::isVegetarian).findFirst();
System.out.println("any = " + any);
}
}
1.5.2.7 归约 reduce
此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个 Integer
。这样的查询可以被归类为归约操作(将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠(fold),因为你可以将这个操
作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3, 6, 8);
//求list中的和,以0为基数
Integer reduce = integers.stream().reduce(0, (a, b) -> a + b);
//Integer的静态方法
int sum = integers.stream().reduce(0, Integer::sum);
System.out.println("reduce = " + reduce);
}
1.5.2.8 最大值和最小值
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3, 6, 8);
Optional<Integer> min = integers.stream().reduce(Integer::min);
System.out.println("min = " + min);
Optional<Integer> max = integers.stream().reduce(Integer::max);
System.out.println("max = " + max);
}
1.5.2.9 Collectors
public static void main(String[] args) {
List<Dish> dish = Dish.getDish();
//创建一个Comparator来进行比较 比较菜的卡路里
Comparator<Dish> dishComparator = Comparator.comparingInt(Dish::getCalories);
//maxBy选出最大值
Optional<Dish> collect = dish.stream().collect(Collectors.maxBy(dishComparator));
System.out.println("collect = " + collect);
//选出最小值
Optional<Dish> collect1 = dish.stream().collect(Collectors.minBy(dishComparator));
System.out.println("collect1 = " + collect1);
}
1.5.2.10 汇总 summingInt
Collectors.summingInt 。它可接受一个把对象映射为求和所需 int 的函数,并返回一个收集器。
public static void main(String[] args) {
List<Dish> dish = Dish.getDish();
//计算总和
int collect = dish.stream().collect(Collectors.summingInt(Dish::getCalories));
System.out.println("collect = " + collect);
}
1.5.2.11 平均数 averagingInt
public static void main(String[] args) {
List<Dish> dish = Dish.getDish();
//计算平均数
Double collect = dish.stream().collect(Collectors.averagingInt(Dish::getCalories));
System.out.println("collect = " + collect);
}
1.5.2.12 连接字符串 joining
public static void main(String[] args) {
List<Dish> dish = Dish.getDish();
String collect = dish.stream().map(Dish::getName).collect(Collectors.joining());
System.out.println("collect = " + collect);
}