Sream API
1、什么是Stream API 及其作用
(1)简介
Stream是Java8中处理数组、集合的抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。当我们使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果。
每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,为我们提供链式编程(流水线式)编程的能力。
2、Stream API 的使用
(1)我先举个栗子:
我们有一个需求:统计一个字符串类型集合中,所有长度大于6的元素个数。
传统实现代码:
package basis.StuStream;
import java.util.ArrayList;
import java.util.List;
public class StuStream {
public static void main(String[] args) {
List<String> datas = new ArrayList<>();
datas.add("hello");
datas.add("hellooo");
datas.add("helloooooo");
datas.add("helloo");
datas.add("hellooooo");
int count = 0;
for (String data : datas) {
if (data.length()>6){
count++;
}
}
System.out.println(count);
}
}
使用Stream API 实现:
package basis.StuStream;
import java.util.ArrayList;
import java.util.List;
public class StuStream {
public static void main(String[] args) {
List<String> datas = new ArrayList<>();
datas.add("hello");
datas.add("hellooo");
datas.add("helloooooo");
datas.add("helloo");
datas.add("hellooooo");
long count = datas.stream()
.filter(data->data.length()>6)
.count();
System.out.println(count);
}
}
从上面的代码中,我们可以看出,使用Stream API 大致分为三步,
- 创建一个Stream。 (创建)
- 在一个或多个步骤中,将初始Stream转化到另一个Stream的中间操作。 (中间操作)
- 使用一个终止操作来产生一个结果。该操作会强制他之前的延迟操作立即执行。在这之后,该Stream就不会在被使用了。(终止操作)
(2)如何获取Stream流
有多种方式生成 Stream Source:
1)从 Collection 和 数组中获取
- Collection.stream()
- Collection.parallelStream()
- Arrays.stream(T array) or Stream.of()
2)从BufferedReader中获取
- java.io.BufferedReader.lines()
3)静态工厂
- java.util.stream.IntStream.range()
- java.nio.file.Files.walk()
4)自己构建
- java.util.Spliterator
5)其它
- Random.ints()
- BitSet.stream()
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()
(3)流的操作
流的操作类型分为两种:
Intermediate(即中间操作):
一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历,这被称为“惰性求值”。
中间操作有:
map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
Terminal(终止操作):
一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
终止操作有:
forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。
需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:
IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long> >、Stream<Double>,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。
Java 8 中还没有提供其它数值型 Stream,因为这将导致扩增的内容较多。而常规的数值型聚合运算可以通过上面三种 Stream 进行。
3、获取Stream流的方法示例
(1)Stream.of() 方法:
//(1)Stream.of方法
Stream<String> stream = Stream.of("hello", "xxx", "yyy", "beijing", "shanghai");
//调用方法
long count = stream.filter(s -> s.length() > 5).count();
System.out.println(count);
Stream.of()接收一个可变的参数,该参数可以是任意类型,返回一个Stream。Stream.of()源码:
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
Stream.of()方法内调用的是Arrays的静态方法stream()。
(2)Arrays.stream()方法:
//(2)Arrays.stream方法
IntStream stream = Arrays.stream(new int[]{100, 200, 300, 400});
double avg = stream.average().getAsDouble();
System.out.println(avg);
Arrays中的静态方法stream()接收一个 int 类型数组作为参数,返回一个IntStream。Arrays.stream()源码:
public static IntStream stream(int[] array) {
return stream(array, 0, array.length);
}
IntStream中:
- average():用于计算数组所有值的平均数;
- getAsDouble():将得到的数据以 double 类型返回;
(3)Collection.stream() 和 Collection.parallelStream()方法
Collection集合接口中有两个用于获取Stream的默认方法,一个是Collection.Stream()方法,用于获取串行的Stream;另一个是Collection.parallel()方法用于获取并行的Stream(),所有Collection集合的实现类都继承了这两个方法。
- Collection.stream():
//(3)使用集合的方法(重点)
List<String> list=new ArrayList<>();
list.add("aaaa");
list.add("bbbb");
list.add("cccc");
list.add("dddd");
Stream<String> stringStream = list.stream();
long count2 = stringStream.count();
System.out.println(count2);
count():终止操作,统计执行了一系列中间操作后集合中元素的个数
源码:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
- Collection.parallelStream():
List<String> list=new ArrayList<>();
list.add("aaaa");
list.add("bbbb");
list.add("cccc");
list.add("dddd");
Stream<String> stream3 = list.parallelStream();
long count1 = stream3.count();
System.out.println(count1);
源码:
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
(4)Stream.iterate
Stream类的静态方法 iterate() 方法也返回一个Stream,可以把这个方法理解为 迭代。
System.out.println("----------迭代-----------");
Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
iterate.limit(10)
.forEach(System.out::println);
上述代码中,我们使用Stream.iterate()生成十个连续的数,每个数在前一个数的基础上加2,第一个参数表示数应该从几开始,第二个参数是生成数的规则的Lambda表达式。
- limit():中间方法,用于限制Stream处理数据的个数;
- forEach():终止方法,遍历。
源码:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
Objects.requireNonNull(f);
final Iterator<T> iterator = new Iterator<T>() {
@SuppressWarnings("unchecked")
T t = (T) Streams.NONE;
@Override
public boolean hasNext() {
return true;
}
@Override
public T next() {
return t = (t == Streams.NONE) ? seed : f.apply(t);
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
iterator,
Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}
可以看出,iterate()方法中使用内部类的方式实现了一个Iterator迭代器。
(5)Stream.generate()
Stream类的静态方法 iterate() 方法也返回一个Stream,可以把这个方法理解为 生成。
System.out.println("--------生成----------");
Stream<Integer> generate = Stream.generate(() -> new Random().nextInt(100));
generate.limit(10)
.forEach(System.out::println);
上述代码生成了10个0~100的随机数,Stream.generate()方法接受一个Supplier(供给型接口)类型的参数,用于定义生成规则。
源码:
public static<T> Stream<T> generate(Supplier<T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}
4、Stream中的常用的中间操作
常用的中间操作:map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
JavaBean:
package StuStream;
public class Employee {
private String name;
private int age;
private double salary;
private String gender;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(String name, int age, double salary, String gender) {
this.name = name;
this.age = age;
this.salary = salary;
this.gender = gender;
}
public Employee() {
}
//省略getter和setter
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", gender='" + gender + '\'' +
'}';
}
}
数据准备:
List<Employee> employees=new ArrayList<>();
employees.add(new Employee("xxx", 30, 10000));
employees.add(new Employee("yyy", 29, 8000));
employees.add(new Employee("zzz", 22, 12000));
employees.add(new Employee("张三", 21, 20000));
employees.add(new Employee("李四", 32, 22000));
employees.add(new Employee("李四", 32, 22000));
(1)filter():过滤,从流中排除元素
过滤出年龄大于25的并遍历输出。
System.out.println("------filter-----");
employees.stream()
.filter(e->e.getAge()>25)
.forEach(System.out::println);
(2)limit():限制。截断流,使其元素不超过给定数量
输出集合中前两个元素。
System.out.println("------limit-----");
employees.stream()
.limit(2)
.forEach(System.out::println);
(3)skip(n):跳过元素。返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流
System.out.println("-------skip------");
employees.stream()
.skip(2)
.limit(2)
.forEach(System.out::println);
(4)distinct():筛选,通过流所生成元素的 equals() 去除重复元素
数据中的最后两个对象属于重复对象,我们要想过滤掉重复的元素,可以使用distinct()方法。
System.out.println("-----distinct------");
employees.stream()
.distinct()
.forEach(System.out::println);
运行结果:
-----distinct------
Employee{name='xxx', age=30, salary=10000.0, gender='null'}
Employee{name='yyy', age=29, salary=8000.0, gender='null'}
Employee{name='zzz', age=22, salary=12000.0, gender='null'}
Employee{name='张三', age=21, salary=20000.0, gender='null'}
Employee{name='李四', age=32, salary=22000.0, gender='null'}
Employee{name='李四', age=32, salary=22000.0, gender='null'}
为什么没有过滤掉的,原因是我们的JavaBean类Employee,没有重写hashcode() 和 equals() 方法,为Employee类添加如下代码:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Double.compare(employee.salary, salary) == 0 &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary);
}
再次运行:
-----distinct------
Employee{name='xxx', age=30, salary=10000.0, gender='null'}
Employee{name='yyy', age=29, salary=8000.0, gender='null'}
Employee{name='zzz', age=22, salary=12000.0, gender='null'}
Employee{name='张三', age=21, salary=20000.0, gender='null'}
Employee{name='李四', age=32, salary=22000.0, gender='null'}
此时,重复的元素被过滤掉了。
(5)map():转换或映射。将数据从一种格式转换成另一种格式。
将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
获取原集合中所有元素的name属性。
System.out.println("-------------map---------------");
employees.stream()
.map(e->e.getName())
.forEach(System.out::println);
结果:
-------------map---------------
xxx
yyy
zzz
张三
李四
李四
将集合中的String类型的元素全部转成大写。
List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
strList.stream()
.map(s->s.toUpperCase())
.forEach(System.out::println);
(6)sorted():对元素进行排序。
使用stored()方法排序有两种方法:
- stored():默认使用自然序排序, 其中的元素必须实现Comparable接口
- sorted(Comparator<? super T> comparator) :我们可以使用lambada 来创建一个Comparator实例。可以按照升序或着降序来排序元素。
1)自然排序
自然排序数据必须是是基本数据类型或String类型的,自然排序按字典顺序进行。
List<Employee> employees=new ArrayList<>();
employees.add(new Employee("5xxx", 30, 10000));
employees.add(new Employee("4yyy", 29, 8000));
employees.add(new Employee("1zzz", 22, 12000));
employees.add(new Employee("c张三", 21, 20000));
employees.add(new Employee("b李四", 32, 24000));
employees.add(new Employee("a李四", 32, 22000));
System.out.println("-----------------自然排序-------------------");
employees.stream()
.map(Employee::getName)
.sorted()
.forEach(System.out::println);
自然排序结果:
1zzz
4yyy
5xxx
a李四
b李四
c张三
2)自定义排序
如果数据引用数据类型的,想要对数据进行排序,就需要我们自定义排序规则:实现方法有两种:
- JavaBean(数据)实现Compareable
- 使用sorted()方法时通过Lambda表达式传入自定义比较器
两种方式是等效的,只不过使用Comparator更加linghuo具体实现
- 实现Compareable:
修改Employee类,让其实现Compareable接口,并重写comparTo方法:
@Override
public int compareTo(Object o) {
int age = this.getAge()-((Employee)o).getAge();
int salary = Double.compare(this.getSalary(),((Employee)o).getSalary());
return age==0?salary:age;
}
排序:
System.out.println("-------自定义排序排序(实现Compareable接口) ---------");
employees.stream()
.sorted()
.forEach(System.out::println);
结果:
Employee{name='c张三', age=21, salary=20000.0, gender='null'}
Employee{name='1zzz', age=22, salary=12000.0, gender='null'}
Employee{name='4yyy', age=29, salary=8000.0, gender='null'}
Employee{name='5xxx', age=30, salary=10000.0, gender='null'}
Employee{name='a李四', age=32, salary=22000.0, gender='null'}
Employee{name='b李四', age=32, salary=24000.0, gender='null'}
- 传入Comparator比较器
employees.stream()
.sorted((o1,o2)->{
int n1=o1.getAge()-o2.getAge();
int n2=Double.compare(o1.getSalary(), o2.getSalary());
return n1==0?n2:n1; })
.forEach(System.out::println);
结果跟Compareable一样,因为两种方式是等效的。
5、Stream中的常用的终止操作
数据准备:
List<Employee> employees=new ArrayList<>();
employees.add(new Employee("xxx", 30, 10000,"男"));
employees.add(new Employee("yyy", 29, 8000,"男"));
employees.add(new Employee("zzz", 22, 12000,"男"));
employees.add(new Employee("张三", 21, 20000,"男"));
employees.add(new Employee("李四", 32, 22000,"妖"));
(1)匹配:返回值均为boolean类型
- allMatch():检查是否匹配所有元素
- anyMatch():检查是否至少匹配一个元素
- noneMatch():检查是否没有匹配的元素
boolean b=employees.stream()
.allMatch(e->e.getGender().equals("男"));
System.out.println("结果:"+b);
boolean b2= employees.stream()
.anyMatch(e -> e.getGender().equals("妖"));
System.out.println(b2);
boolean b3= employees.stream()
.noneMatch(e -> e.getGender().equals("女"));
System.out.println(b3);
(2)返回流中的一个元素,返回类型均为Optional类型的
- findFirst():返回第一个元素
- findAny():返回当前流中的任意元素
Optional<Employee> first = employees.stream()
.findFirst();
System.out.println(first.get().toString());
Optional<Employee> any = employees.stream()
.findAny();
System.out.println(any.get().toString());
findAny()方法在串行和并行流中返回任意一个数据时,总是返回最快得到的那个元素。
Optional类:Java 8 中为了解决空指针异常而引入的类。
- Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
- Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
- Optional 类的引入很好的解决空指针异常。
(3)最值:获取流中的最大值最小值,参数为Comparator类型
- max():返回流中最大值
- min():返回流中最小值
返回工资最高的员工:
Optional<Employee> max = employees.stream()
.max((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary()));
System.out.println(max.get().toString());
返回工资最低的员工:
Optional<Employee> min = employees.stream()
.min((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary()));
System.out.println(min.get().toString());
(4)reduce():规约
这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
//数值求和
List<Integer> list= Arrays.asList(10,20,30,40);
Integer sum = list.stream()
.reduce(10, (x, y) -> x + y);
System.out.println(sum);
//字符串拼接
List<String> strings= Arrays.asList("aa","bb","cc","dd");
String str = strings.stream()
.reduce("aa", (x, y) -> x + y);
System.out.println(str);
//计算所有员工工资总和
Double salay = employees.stream()
.map(Employee::getSalary)
.reduce(0.0, Double::sum);
System.out.println(salay);
(5)Collect:把数据按照指定的集合类型进行返回。
获取所有的员工姓名的List集合
List<String> collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println(collect);
获取所有的员工姓名的Set集合:Collectors.toSet()方法。
6、Collectors类
用 Collectors 来进行 reduction 操作
java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。
1、groupingBy():按照指定的条件分组
返回一个Map,键为分组条件,值为一个单列集合Collection,表示满足分组条件的元素
按年龄将员工分组:
package StuStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/*
* wgy 2019/8/17 15:30
*/
public class Demo4 {
public static void main(String[] args) {
List<Employee> employees=new ArrayList<>();
employees.add(new Employee("xxx", 30, 10000,"男"));
employees.add(new Employee("yyy", 21, 8000,"男"));
employees.add(new Employee("zzz", 22, 12000,"男"));
employees.add(new Employee("张三", 21, 20000,"男"));
employees.add(new Employee("李四", 22, 22000,"妖"));
Map<Integer,List<Employee>> map = employees.stream()
.collect(Collectors.groupingBy(e->e.getAge()));
Set<Integer> key = map.keySet();
for (Integer o : key) {
System.out.println(o+"="+map.get(o));
}
}
}
结果:
21=[Employee{name='yyy', age=21, salary=8000.0, gender='男'}, Employee{name='张三', age=21, salary=20000.0, gender='男'}]
22=[Employee{name='zzz', age=22, salary=12000.0, gender='男'}, Employee{name='李四', age=22, salary=22000.0, gender='妖'}]
30=[Employee{name='xxx', age=30, salary=10000.0, gender='男'}]
2、partitioningBy():按照指定的条件分区
返回一个Map,键为分区条件,值为一个单列集合Collection,表示满足分区条件的元素
按工资是否大于12000将员工分为两部分:
Map<Boolean,List<Employee>> map1 = employees.stream()
.collect(Collectors.partitioningBy(e->e.getSalary()>12000));
Set<Boolean> key1 = map1.keySet();
for (Boolean o : key1) {
System.out.println(o+"="+map1.get(o));
}
false=[Employee{name='xxx', age=30, salary=10000.0, gender='男'}, Employee{name='yyy', age=21, salary=8000.0, gender='男'}, Employee{name='zzz', age=22, salary=12000.0, gender='男'}]
true=[Employee{name='张三', age=21, salary=20000.0, gender='男'}, Employee{name='李四', age=22, salary=22000.0, gender='妖'}]
Collectors类还有很多强大的功能,后续博客可能会专门介绍(如果有时间的话)。