Java8新特性之Stream API
1、Stream API的基本介绍
Java 8 API添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream 是Java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的查找、过滤、筛选等操作。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
1.1、什么是stream(流)
流(Stream)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,流讲的是计算。
注意:
- Stream是无存储的。stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
- Stream不会改变源对象。相反,他会返回一个持有结果的新Stream。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。
- 惰式执行。stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
- 可消费性。stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。
2、Stream的操作步骤
Stream有如下三个操作步骤:
- 创建Stream:从一个数据源,如集合、数组中获取流。
- 中间操作:一个操作的中间链,对数据源的数据进行操作。
- 终止操作:一个终止操作,执行中间操作链,并产生结果。
操作步骤如下图:
要注意的是,对流的操作完成后需要进行关闭操作(或者用JAVA7的try-with-resources)。
3、创建 stream
在 Java 8 中, 集合接口有两个方法来生成流:
-
stream() − 为集合创建串行流。
-
parallelStream() − 为集合创建并行流。
4、中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理,而在终止操作时一次性全部处理,称为“惰性求值”。
常见的中间操作有以下几种:
- 筛选与切片
- 映射
- 排序
下面的所有示例中基于的类和变量如下:
public class Person { private String name; private Integer age; private String country; private char sex; public Person(String name, Integer age, String country, char sex) { this.name = name; this.age = age; this.country = country; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", country='" + country + '\'' + ", sex=" + sex + '}'; } }
personList 集合变量:
List<Person> personList = new ArrayList<>(); personList.add(new Person("欧阳雪",18,"中国",'F')); personList.add(new Person("Tom",24,"美国",'M')); personList.add(new Person("Harley",22,"英国",'F')); personList.add(new Person("向天笑",20,"中国",'M')); personList.add(new Person("李康",22,"中国",'M')); personList.add(new Person("小梅",20,"中国",'F')); personList.add(new Person("何雪",21,"中国",'F')); personList.add(new Person("李康",22,"中国",'M'));
4.1、筛选与切片
方法如下:
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,从流中排除某些元素 |
limit(long maxSize) | 截断流,使其元素不超过给定对象 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补 |
distinct() | 筛选,去除重复元素。会通过流所生成元素的hashcode()和equals()方法来判断是否为同一元素,所以要想去除重复,可能需要覆写类中这两个方法。 |
示例:
- filter 和 Limit 使用示例:
personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println); //从Person列表中取出两个女性
输出结果:
Person{name='欧阳雪', age=18, country='中国', sex=F}
Person{name='Harley', age=22, country='英国', sex=F}
- skip 使用示例:
personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println); //先从Person列表中过滤出女性列表,然后排除第一个元素
输出结果:
Person{name='Harley', age=22, country='英国', sex=F} Person{name='小梅', age=20, country='中国', sex=F} Person{name='何雪', age=21, country='中国', sex=F}
- distinct 使用示例:
personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);
如果我们没有覆写 person 类中的 equals() 或者 hashcode() 方法,则判断是否为同一元素的依据可能无法照我们所想的那样,最终输出结果会如下:
Person{name='Tom', age=24, country='美国', sex=M} Person{name='向天笑', age=20, country='中国', sex=M} Person{name='李康', age=22, country='中国', sex=M} Person{name='李康', age=22, country='中国', sex=M}
可以发现,同一元素 "李康" 并没有被去除。我们可以在 person 类中覆写 equals() 方法:
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return sex == person.sex && Objects.equals(name, person.name) && Objects.equals(age, person.age) && Objects.equals(country, person.country); }
然后再执行 distinct 示例,输出结果如下:
Person{name='Tom', age=24, country='美国', sex=M} Person{name='向天笑', age=20, country='中国', sex=M} Person{name='李康', age=22, country='中国', sex=M}
可以看到正常去除了重复元素。
4.2、映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素并返回 |
mapToDouble(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream并返回 |
mapToInt(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream并返回 |
mapToLong(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream并返回 |
flatMap(Function f) |
接收一个函数作为参数,将流中的每个值都换成另一个流,然后将所有流连接成一个流 |
map在接收到流后,直接将Stream放入到一个Stream中,最终整体返回一个包含了多个Stream的Stream。flatMap 和 map 等映射方法不一样,flatMap 在接收到Stream后,会将接收到的Stream中的每个元素取出来放入一个Stream中,最后一次性将一个包含多个元素的Stream返回。所以说,flatMap 最后输出的可能会是一个元素,而不是像 map 是多个元素。
map 使用示例:
personList.stream().map(n -> n.getName()).forEach(n -> System.out.println(n)); //将输出 personlist 所有元素的 name 属性
mapToInt 使用示例:
personList.stream().mapToInt(n -> n.getAge()).forEach(n -> System.out.println(n)); //将输出personlist所有元素的age属性
4.3、排序
5、终止操作
5.1、查找与匹配
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素,返回一个Boolean值 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素,返回一个Boolean值 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反, Stream API 使用内部迭代) |
allMatch、anyMatch 使用示例:
boolean flage = personList.stream().allMatch(p -> p.getAge() >= 21); //false boolean flage2 = personList.stream().anyMatch(p -> p.getAge() >= 21); //true
max、min使用示例:
Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge())); System.out.println("年龄最大的人信息:" + maxAge.get()); //输出:年龄最大的人信息:Person{name='Tom', age=24, country='美国', sex=M} Optional<Person> minAge = personList.stream().min((p1, p2) -> p1.getAge().compareTo(p2.getAge())); System.out.println("年龄最小的人信息:" + minAge.get()); //输出:年龄最小的人信息:Person{name='欧阳雪', age=18, country='中国', sex=F}
5.2、归纳
5.3、收集
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。
Collector 实用类提供了很多静态方法可以方便的收集:
Collectors.toList 使用示例:
final List<String> collect = personList.stream().map(p -> p.getCountry()).distinct().collect(Collectors.toList()); System.out.println(collect); //输出:[中国, 美国, 英国]
Collectors.averagingInt 使用示例:
final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge())); System.out.println("平均年龄为:" + collect1); //输出:平均年龄为:21.125
final Optional<Integer> maxAge2 = personList.stream().map(Person::getAge).collect(Collectors.maxBy(Integer::compareTo)); System.out.println(maxAge2.get()); //输出:24
Collectors.groupingBy 方法接收一个函数,在该函数的函数体里指定根据什么来进行分组。该方法返回一个map,key 为指定的用于分组的属性中各个分组的值,value为 list,包含各个分组的元素。
使用示例:
Map<String, List<Person>> map= personList.stream().collect(Collectors.groupingBy(p -> p.getCountry()));
System.out.println(map);
输出结果:
{ 美国=[Person{name='Tom', age=24, country='美国', sex=M}], 中国=[ Person{name='欧阳雪', age=18, country='中国', sex=F}, Person{name='向天笑', age=20, country='中国', sex=M}, Person{name='李康', age=22, country='中国', sex=M}, Person{name='小梅', age=20, country='中国', sex=F}, Person{name='何雪', age=21, country='中国', sex=F}, Person{name='李康', age=22, country='中国', sex=M} ], 英国=[Person{name='Harley', age=22, country='英国', sex=F}] }