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的一些基本操作,只要勤加练习就可以灵活使用,而且效率大大提高。