java8新特性——Stream API
Java8中有两大最为重要得改变,其一时Lambda表达式,另外就是 Stream API了。在前面几篇中简单学习了Lambda表达式得语法,以及函数式接口。本文就来简单学习一下Stream API(java.util.stream.*)。
Stream 是 Java8中处理集合得关键抽象概念,他可以指定你希望对集合进行得操作,可以执行非常复杂得查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似使用SQL执行得数据库查询。也可以使用S他ream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用得处理数据得方式。
在Stream操作过程中,可以对数据流做过滤,排序,切片等操作,但是操作之后会产生一个新的流,而数据源则不会发生改变。
一、什么是 Stream
Stream是数据渠道,用于操作数据源(集合,数组等)所生成得元素序列。而集合讲得是数据,流讲得是计算。
注意:
①. Stream 自己不会存储元素。
②. Stream 不会改变源对象。相反,它会返回一个持有结果得新Stream
③. Stream 操作时延迟执行得,这意味着它们会等到需要结果时才执行。(延迟加载)
二、Stream 操作的三个步骤
1). 创建 Stream
一个数据源(集合,数组),获取一个流。
2). 中间操作
一个中间操作链,对数据源的数据进行处理。
3). 终止操作
一个终止操作,执行中间操作链,并产生结果。
三、创建Stream 的四种方式
1). 通过Collection得Stream()方法(串行流)或者 parallelStream()方法(并行流)创建Stream。
1 /** 2 * 创建 Stream的四种方式 3 * 1.通过Collection得Stream()方法(串行流) 4 或者 parallelStream()方法(并行流)创建Stream 5 */ 6 @Test 7 public void test1 () { 8 9 //1. 通过Collection得Stream()方法(串行流) 10 //或者 parallelStream()方法(并行流)创建Stream 11 List<String> list = new ArrayList<String>(); 12 Stream<String> stream1 = list.stream(); 13 14 Stream<String> stream2 = list.parallelStream(); 15 16 }
2).通过Arrays中得静态方法stream()获取数组流
1 /** 2 * 创建 Stream的四种方式 3 * 2. 通过Arrays中得静态方法stream()获取数组流 4 */ 5 @Test 6 public void test2 () { 7 8 //2. 通过Arrays中得静态方法stream()获取数组流 9 IntStream stream = Arrays.stream(new int[]{3,5}); 10 11 }
3). 通过Stream类中得 of()静态方法获取流
1 /** 2 * 创建 Stream的四种方式 3 * 3. 通过Stream类中得 of()静态方法获取流 4 */ 5 @Test 6 public void test3 () { 7 8 //3. 通过Stream类中得 of()静态方法获取流 9 Stream<String> stream = Stream.of("4645", "huinnj"); 10 11 }
4). 创建无限流(迭代、生成)
1 /** 2 * 创建 Stream的四种方式 3 * 4. 创建无限流(迭代、生成) 4 */ 5 @Test 6 public void test4 () { 7 8 //4. 创建无限流 9 //迭代(需要传入一个种子,也就是起始值,然后传入一个一元操作) 10 Stream<Integer> stream1 = Stream.iterate(2, (x) -> x * 2); 11 12 //生成(无限产生对象) 13 Stream<Double> stream2 = Stream.generate(() -> Math.random()); 14 15 }
四、Stream 中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何得处理!而终止操作时一次性全部处理,称为‘延迟加载’
1). 筛选与切片
①. filter —— 接收Lambda ,从流中排除某些元素。
1 /** 2 * 筛选与切片 3 * filter —— 接收Lambda ,从流中排除某些元素。 4 * 5 */ 6 @Test 7 public void test5 () { 8 //内部迭代:在此过程中没有进行过迭代,由Stream api进行迭代 9 //中间操作:不会执行任何操作 10 Stream<Person> stream = list.stream().filter((e) -> { 11 System.out.println("Stream API 中间操作"); 12 return e.getAge() > 30; 13 }); 14 15 //终止操作:只有执行终止操作才会执行全部。即:延迟加载 16 stream.forEach(System.out :: println); 17 18 }
执行上面方法,得到下面结果。
Person [name=张三, sex=男, age=76] Stream API 中间操作 Stream API 中间操作 Person [name=王五, sex=男, age=35] Stream API 中间操作 Stream API 中间操作 Person [name=钱七, sex=男, age=56] Stream API 中间操作 Person [name=翠花, sex=女, age=34]
我们,在执行终止语句之后,一边迭代,一边打印,而我们并没有去迭代上面集合,其实这是内部迭代,由Stream API 完成。
下面我们来看看外部迭代,也就是我们人为得迭代。
1 @Test 2 public void test6 () { 3 //外部迭代 4 Iterator<Person> it = list.iterator(); 5 while (it.hasNext()) { 6 System.out.println(it.next()); 7 } 8 9 }
②. limit —— 截断流,使其元素不超过给定数量。
1 /** 2 * limit —— 截断流,使其元素不超过给定数量。 3 */ 4 @Test 5 public void test7 () { 6 //过滤之后取2个值 7 list.stream().filter((e) -> e.getAge() >30 ). 8 limit(2).forEach(System.out :: println); 9 10 }
在这里,我们可以配合其他得中间操作,并截断流,使我们可以取得相应个数得元素。而且在上面计算中,只要发现有2条符合条件得元素,则不会继续往下迭代数据,可以提高效率。
2). 跳过元素
skip(n),返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空,与limit(n)互补。
1 /** 2 * skip(n)—— 跳过元素,返回一个扔掉了前n个元素的流。 3 * 若流中元素不足n个,则返回一个空,与limit(n)互补。 4 */ 5 @Test 6 public void test8 () { 7 //跳过前2个值 8 list.stream().skip(2).forEach(System.out :: println); 9 10 } 11
3). 筛选
distinct 通过流所生成元素的hashCode()和equals()去除重复元素
1 /** 2 * distinct —— 筛选,通过流所生成元素的hashCode()和equals()去除重复元素 3 */ 4 @Test 5 public void test9 () { 6 7 list.stream().distinct().forEach(System.out :: println); 8 9 }
注意:distinct 需要实体中重写hashCode()和 equals()方法才可以使用
4). 映射
① . map ,将元素转换成其他形式或者提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
1 /** 2 * map —— 映射 ,将元素转换成其他形式或者提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 3 */ 4 @Test 5 public void test10 () { 6 //将流中每一个元素都映射到map的函数中,每个元素执行这个函数,再返回 7 List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd"); 8 list.stream().map((e) -> e.toUpperCase()).forEach(System.out::printf); 9 10 //获取Person中的每一个人得名字name,再返回一个集合 11 List<String> names = this.list.stream().map(Person :: getName). 12 collect(Collectors.toList()); 13 }
② . flatMap —— 接收一个函数作为参数,将流中的每个值都换成一个流,然后把所有流连接成一个流
1 /** 2 * flatMap —— 接收一个函数作为参数,将流中的每个值都换成一个流,然后把所有流连接成一个流 3 */ 4 @Test 5 public void test11 () { 6 StreamAPI_Test s = new StreamAPI_Test(); 7 List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd"); 8 list.stream().flatMap((e) -> s.filterCharacter(e)).forEach(System.out::println); 9 10 //如果使用map则需要这样写 11 list.stream().map((e) -> s.filterCharacter(e)).forEach((e) -> { 12 e.forEach(System.out::println); 13 }); 14 } 15 16 /** 17 * 将一个字符串转换为流 18 * @param str 19 * @return 20 */ 21 public Stream<Character> filterCharacter(String str){ 22 List<Character> list = new ArrayList<>(); 23 for (Character ch : str.toCharArray()) { 24 list.add(ch); 25 } 26 return list.stream(); 27 }
其实map方法就相当于Collaction的add方法,如果add的是个集合得话就会变成二维数组,而flatMap 的话就相当于Collaction的addAll方法,参数如果是集合得话,只是将2个集合合并,而不是变成二维数组。
5). 排序
sorted有两种方法,一种是不传任何参数,叫自然排序,还有一种需要传Comparator 接口参数,叫做定制排序。
1 /** 2 * sorted有两种方法,一种是不传任何参数,叫自然排序,还有一种需要传Comparator 接口参数,叫做定制排序。 3 */ 4 @Test 5 public void test12 () { 6 // 自然排序 7 List<Person> persons = list.stream().sorted().collect(Collectors.toList()); 8 9 //定制排序 10 List<Person> persons1 = list.stream().sorted((e1, e2) -> { 11 if (e1.getAge() == e2.getAge()) { 12 return 0; 13 } else if (e1.getAge() > e2.getAge()) { 14 return 1; 15 } else { 16 return -1; 17 } 18 }).collect(Collectors.toList()); 19 }
五、Stream 终止操作
1). 查找与匹配
首先我们先创建一个集合。
1 List<Person> persons = Arrays.asList( 2 new Person("张三", "男", 76, Status.FREE), 3 new Person("李四", "女", 12, Status.BUSY), 4 new Person("王五", "男", 35, Status.BUSY), 5 new Person("赵六", "男", 3, Status.FREE), 6 new Person("钱七", "男", 56, Status.BUSY), 7 new Person("翠花", "女", 34, Status.VOCATION), 8 new Person("翠花", "女", 34, Status.FREE), 9 new Person("翠花", "女", 34, Status.VOCATION) 10 );
①. allMatch —— 检查是否匹配所有元素。
1 /** 2 * allMatch —— 检查是否匹配所有元素。 3 * 判断所有状态是否都是FREE 4 */ 5 @Test 6 public void test13 () { 7 boolean b = persons.stream().allMatch((e) -> Status.FREE.equals(e.getStatus())); 8 System.out.println(b); 9 }
②. anyMatch —— 检查是否至少匹配所有元素。
1 /** 2 * anyMatch —— 检查是否至少匹配所有元素。 3 * 判断是否有一个是FREE 4 */ 5 @Test 6 public void test14 () { 7 boolean b = persons.stream().anyMatch((e) -> Status.FREE.equals(e.getStatus())); 8 System.out.println(b); 9 }
③. noneMatch —— 检查是否没有匹配所有元素。
1 /** 2 * noneMatch —— 检查是否没有匹配所有元素。 3 * 判断是否没有FREE 4 */ 5 @Test 6 public void test15 () { 7 boolean b = persons.stream().noneMatch((e) -> Status.FREE.equals(e.getStatus())); 8 System.out.println(b); 9 }
④. findFirst —— 返回第一个元素。
1 /** 2 * findFirst —— 返回第一个元素。 3 * 4 */ 5 @Test 6 public void test16 () { 7 Optional<Person> person = persons.stream().findFirst(); 8 System.out.println(person); 9 10 person.orElse(new Person("王五", "男", 35, Status.BUSY)); 11 }
注意:上面findFirst 返回的是一个Optional的对像,他将我们的Person封装了一层,这是为了避免空指针。而且这个对象为我们提供了一个orElse方法,就是当我们得到的这个对象为空时,我们可以传入一个新得对象去替代它。
⑤. findAny —— 返回当前流中任意元素。
1 /** 2 * findAny —— 返回当前流中任意元素。 3 */ 4 @Test 5 public void test17 () { 6 Optional<Person> person = persons.stream().findAny(); 7 System.out.println(person); 8 9 person.orElse(new Person("王五", "男", 35, Status.BUSY)); 10 }
⑥. count —— 返回流中元素总个数。
1 /** 2 * count —— 返回流中元素总个数。 3 */ 4 @Test 5 public void test18 () { 6 long count = persons.stream().count(); 7 System.out.println(count); 8 9 }
⑦. max —— 返回流中最大值。
1 /** 2 * max —— 返回流中最大值。 3 */ 4 @Test 5 public void test18 () { 6 Optional<Person> person = persons.stream().max((e1, e2) -> Double.compare(e1.getAge(), e2.getAge())); 7 System.out.println(person); 8 9 }
⑧. min —— 返回流中最小值。
1 /** 2 * min —— 返回流中最小值。 3 */ 4 @Test 5 public void test20 () { 6 Optional<Person> person = persons.stream().min((e1, e2) -> Double.compare(e1.getAge(), e2.getAge())); 7 System.out.println(person); 8 9 }
2). 归约(可以将流中元素反复结合在一起,得到一个值)
①. reduce(T identitty,BinaryOperator)首先,需要传一个起始值,然后,传入的是一个二元运算。
1 /** 2 * reduce(T identitty,BinaryOperator)首先,需要传一个起始值,然后,传入的是一个二元运算。 3 */ 4 @Test 5 public void test21 () { 6 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 7 Integer sum = list.stream().reduce(0, (x, y) -> x + y); 8 }
②. reduce(BinaryOperator)此方法相对于上面方法来说,没有起始值,则有可能结果为空,所以返回的值会被封装到Optional中。
1 /** 2 * reduce(BinaryOperator)此方法相对于上面方法来说,没有起始值,则有可能结果为空,所以返回的值会被封装到Optional中 3 */ 4 @Test 5 public void test22 () { 6 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 7 Optional<Integer> sum = list.stream().reduce(Integer :: sum); 8 }
备注:map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名。
3). 收集collect(将流转换为其他形式。接收一个Collector接口得实现,用于给其他Stream中元素做汇总的方法)
Collector接口中方法得实现决定了如何对流执行收集操作(如收集到List,Set,Map)。但是Collectors实用类提供了很多静态方法,可以方便地创建常见得收集器实例。
①. Collectors.toList() 将流转换成List
1 /** 2 * Collectors.toList() 将流转换成List 3 */ 4 @Test 5 public void test23() { 6 7 List<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toList()); 8 }
②. Collectors.toSet()将流转换为Set
1 /** 2 * Collectors.toSet() 将流转换成Set 3 */ 4 @Test 5 public void test24() { 6 7 Set<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toSet()); 8 }
③. Collectors.toCollection()将流转换为其他类型的集合
1 /** 2 * Collectors.toCollection()将流转换为其他类型的集合 3 */ 4 @Test 5 public void test25() { 6 7 LinkedList<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toCollection(LinkedList :: new)); 8 }
④. Collectors.counting() 元素个数
1 /** 2 * Collectors.counting() 总数 3 */ 4 @Test 5 public void test26() { 6 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 7 Long count = list.stream().collect(Collectors.counting()); 8 }
⑤. Collectors.averagingDouble()、Collectors.averagingDouble()、Collectors.averagingLong() 平均数,这三个方法都可以求平均数,不同之处在于传入得参数类型不同,返回值都为Double
1 /** 2 * Collectors.averagingInt() 、 3 * Collectors.averagingDouble()、 4 * Collectors.averagingLong() 平均数, 5 * 者三个方法都可以求平均数,不同之处在于传入得参数类型不同, 6 * 返回值都为Double 7 */ 8 @Test 9 public void test27() { 10 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 11 Double avg = list.stream().collect(Collectors.averagingInt((x) -> x)); 12 }
⑥. Collectors.summingDouble()、Collectors.summingDouble()、Collectors.summingLong() 求和,不同之处在于传入得参数类型不同,返回值为Integer, Double, Long
1 /** 2 * Collectors.summingInt() 、 3 * Collectors.summingDouble()、 4 * Collectors.summingLong() 求和, 5 * 者三个方法都可以求总数,不同之处在于传入得参数类型不同, 6 * 返回值为Integer, Double, Long 7 */ 8 @Test 9 public void test28() { 10 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 11 Integer sum = list.stream().collect(Collectors.summingInt((x) -> x)); 12 }
⑦. Collectors.maxBy() 求最大值
1 /** 2 * Collectors.maxBy() 求最大值 3 */ 4 @Test 5 public void test29() { 6 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 7 Optional<Integer> max = list.stream().collect(Collectors.maxBy((x, y) ->Integer.compare(x, y))); 8 }
⑧. Collectors.minBy() 求最小值
/** * Collectors.minBy() 求最小值 */ @Test public void test29() { List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Optional<Integer> min = list.stream().collect(Collectors.minBy((x, y) ->Integer.compare(x, y))); }
⑨. Collectors.groupingBy()分组 ,返回一个map
1 /** 2 * Collectors.groupingBy()分组 ,返回一个map 3 */ 4 @Test 5 public void test30() { 6 Map<String, List<Person>> personMap = list.stream().collect(Collectors.groupingBy(Person :: getSex)); 7 }
Collectors.groupingBy()还可以实现多级分组
1 /** 2 * Collectors.groupingBy()多级分组 ,返回一个map 3 */ 4 @Test 5 public void test31() { 6 Map<String, Map<Status, List<Person>>> personMap = list.stream().collect(Collectors.groupingBy(Person :: getSex, Collectors.groupingBy(Person :: getStatus))); 7 }
⑩. Collectors.partitioningBy() 分区,参数中传一个函数,返回true,和false 分成两个区
1 /** 2 * Collectors.partitioningBy() 分区,参数中传一个函数,返回true,和false 分成两个区 3 */ 4 @Test 5 public void test32() { 6 Map<Boolean, List<Person>> personMap = list.stream().collect(Collectors.partitioningBy((x) -> x.getAge() > 30)); 7 }
上面就是Stream的一些基本操作,只要勤加练习就可以灵活使用,而且效率大大提高。