概述
Stream API是java8的新特性,用于对数据元素的处理。集合关注的是数据的存储,与内存打交道,而Stream关注的是对数据的运算,与CPU打交道。
Stream的执行流程如下:
- Stream的实例化
- 一系列的中间操作(包括过滤、映射等)
- 终止操作
Stream具有一下特点: - 自己不会存储元素
- 不会改变原对象
- 操作时延迟执行的,即在执行终止操作时才会执行一系列的中间操作
下面是一个Person类,后续对数据元素的操作涉及到该类的实例化对象
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Person {
public String name;
public Integer age;
public double salary;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Double.compare(person.salary, salary) == 0 &&
Objects.equals(name, person.name) &&
Objects.equals(age, person.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary);
}
}
Stream的实例化
Stream的实例化可以分为四种方式:
- 通过集合创建
- 通过数组创建
- 通过Stream的of()方法创建
- 创建无限流
通过集合
初始化数据:
List<Person> list = new ArrayList<>();
list.add(new Person("张三",18,3143.20));
list.add(new Person("李四",32,10941.43));
list.add(new Person("王五",45,6439.23));
list.add(new Person("钱六",28,9333.40));
- 调用Collection接口的默认方法default Stream<E> stream()获取顺序流
Stream<Person> stream = list.stream();
- 调用Collection接口的默认方法default Stream<E> parallelStream()获取并行流
Stream<Person> parallelStream = list.parallelStream();
并行流与顺序流的区别在于,顺序流由当前线程按顺序对元素进行操作;并行流内部以多线程的方式对元素进行操作。
通过数组
通过调用Arrays类的static <T> Stream<T> stream(T[] array)方法创建Stream
//int
int[] arr = new int[]{1,2,3,4,5};
IntStream stream1 = Arrays.stream(arr);
//Person
Person u1 = new Person("张三", 45, 5312.67);
Person u2 = new Person("李四", 45, 5312.67);
Person[] list = {u1, u2};
Stream<Person> stream2 = Arrays.stream(list);
对于int、double、long三种类型数组会直接创建对应的流,而自定义的数据通过泛型表示对应的流
通过Stream.of()方法
直接调用Stream的of()静态方法创建流
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
可以看到其本质是调用了Arrays.stream()
使用示例如下:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
创建无限流
调用Stream类的iterate()和generate()可用于创建无限流,这两种方法一般用于造数据而不是对数据进行处理
- iterate迭代
//偶数
Stream<Integer> iterate = Stream.iterate(4, t -> t + 2);
//遍历前十个
iterate.limit(3).forEach(System.out::println);
其中第一个参数为初始值,后续对此值进行+2处理得到一个流。第二行涉及中间操作和终止操作,后续会介绍。作用为打印前10条数据
结果如下:
4
6
8
- generate生成
//生成随机数
Stream<Double> generate = Stream.generate(Math::random);
//打印前三个
generate.limit(3).forEach(System.out::println);
示例生成随机数,参数Math::random为方法引用。后续打印前三条数据结果如下:
0.8008846067367479
0.16378517069415766
0.21271395557491268
中间操作
中间操作包括筛选与切片、映射、排序
筛选与切片
准备数据:
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三",18,3143.20));
list.add(new Person("李四",32,10941.43));
list.add(new Person("王五",45,6439.23));
list.add(new Person("钱六",28,9333.40));
测试相关中间操作,包括过滤、截断、跳过、去重
//filter(Predicate p) 接收lambda,从流中排除某些元素
//过滤薪水超过7000的
list.stream().filter(p -> p.getSalary() > 7000).forEach(System.out::println);
System.out.println("-----------------------------");
//limit(n) 截断流,使其元素数量不超过指定数量
//取前两个元素
list.stream().limit(2).forEach(System.out::println);
System.out.println("-----------------------------");
//skip(n) 跳过元素,返回一个扔掉前n个元素的流。若流中元素不足n个,则返回一个空流
//跳过前三个元素
list.stream().skip(3).forEach(System.out::println);
System.out.println("-----------------------------");
//distinct() 筛选,通过流所生成的元素的hashCode()和equals()去除重复元素
//新增一条重复数据后去重
list.add(new Person("张三",18,3143.20));
list.stream().distinct().forEach(System.out::println);
结果如下:
Person(name=李四, age=32, salary=10941.43)
Person(name=钱六, age=28, salary=9333.4)
-----------------------------
Person(name=张三, age=18, salary=3143.2)
Person(name=李四, age=32, salary=10941.43)
-----------------------------
Person(name=钱六, age=28, salary=9333.4)
-----------------------------
Person(name=张三, age=18, salary=3143.2)
Person(name=李四, age=32, salary=10941.43)
Person(name=王五, age=45, salary=6439.23)
Person(name=钱六, age=28, salary=9333.4)
映射
映射包括map()方法和flatMap()方法
map(Function f)接收一个函数作为参数,将元素转换为其他形式或提取信息,该函数会被应用到每个元素并将其映射成一个新元素
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
//将每个元素映射为大写
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
结果如下:
AA
BB
CC
DD
flatMap(Function f)接收一个函数作为参数,将流中每个值都换成另一个流,然后将所有的流连接成一个流。即如果流对象中的元素仍然是流,则将元素中的流展开。
//将一个字符串转化为流,操作元素为字符
public Stream<Character> stringToStream(String s){
ArrayList<Character> list = new ArrayList<>();
for (char c : s.toCharArray()) {
list.add(c);
}
return list.stream();
}
将上述list中的每个字符串转化为流,如果想要打印所有的元素使用flatMap函数如下:
Stream<Character> characterStream = list.stream().flatMap(str -> stringToStream(str));
characterStream.forEach(System.out::println);
可以看到返回的流的泛型为Character,这是因为将字符串转化的流展开了,连接为了一个流
打印结果如下:
a
a
b
b
c
c
d
d
如果使用map()函数则需要先遍历内层流,然后才能遍历流中的元素:
Stream<Stream<Character>> streamStream = list.stream().map(str -> stringToStream(str));
streamStream.forEach(s -> {
s.forEach(System.out::println);
});
排序
排序可分为自然排序sort()和定制排序sort(Comparator c)
自然排序的元素需要实现Comparable接口,按照接口规则进行排序
定制排序则根据自己的比较器Comparator进行排序
//sorted() 自然排序
List<Integer> list = Arrays.asList(12, 43, 56, 25, 86, 35, 21);
list.stream().sorted().forEach(System.out::println);
结果如下:
12
21
25
35
43
56
86
//sorted(Comparator com) 定制排序
ArrayList<Person> list1 = new ArrayList<>();
list1.add(new Person("张三",18,3143.20));
list1.add(new Person("李四",32,10941.43));
list1.add(new Person("王五",45,6439.23));
list1.add(new Person("钱六",28,9333.40));
//按照年龄从小到大排序
list1.stream().sorted((p1,p2)-> p1.getAge()-p2.getAge()).forEach(System.out::println);
定制排序结果如下:
Person(name=张三, age=18, salary=3143.2)
Person(name=钱六, age=28, salary=9333.4)
Person(name=李四, age=32, salary=10941.43)
Person(name=王五, age=45, salary=6439.23)
终止操作
需要说明只有执行了终止操作,中间操作才会执行,即前述延迟执行
终止操作包括匹配与查找、归约、收集
匹配与查找
准备数据:
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三",18,3143.20));
list.add(new Person("李四",32,10941.43));
list.add(new Person("王五",45,6439.23));
list.add(new Person("钱六",28,9333.40));
方法较为简单,全部一起测试
测试allMatch、anyMatch、noneMatch、findFirst、findAny、count、max、min、forEach
//allMatch(Predicate p) 检查是否匹配所有元素
//所有人年龄大于20,是返回true,否返回false
boolean allMatch = list.stream().allMatch(p -> p.getAge() > 20);
System.out.println(allMatch);
//anyMatch(Predicate p) 检查是否至少匹配一个元素
//存在任何一个人薪资大于10000,是返回true,否返回false
boolean anyMatch = list.stream().anyMatch(p -> p.getSalary() > 10000);
System.out.println(anyMatch);
//noneMatch(Predicate p) 检查是否没有匹配的元素
//没有人年龄小于12,是返回true,否返回false
boolean noneMatch = list.stream().noneMatch(p -> p.getAge() < 12);
System.out.println(noneMatch);
//findFirst 返回第一个元素
Optional<Person> first = list.stream().findFirst();
System.out.println(first);
//findAny 返回当前流中的任意元素
Optional<Person> any = list.stream().findAny();
System.out.println(any);
//count 返回流中元素的总数
long count = list.stream().count();
System.out.println(count);
//max(Comparator c) 返回流中最大值
//返回薪资最大的人
Optional<Person> max = list.stream().max((p1, p2) -> (int) (p1.getSalary() - p2.getSalary()));
System.out.println(max);
//min(Comparator c) 返回流中最小值
//返回年龄最小的人
Optional<Person> min = list.stream().min((p1, p2) -> p1.getAge() - p2.getAge());
System.out.println(min);
//forEach(Consumer c) 内部迭代
list.stream().forEach(System.out::println);
结果如下:
false
true
true
Optional[Person(name=张三, age=18, salary=3143.2)]
Optional[Person(name=张三, age=18, salary=3143.2)]
4
Optional[Person(name=李四, age=32, salary=10941.43)]
Optional[Person(name=张三, age=18, salary=3143.2)]
Person(name=张三, age=18, salary=3143.2)
Person(name=李四, age=32, salary=10941.43)
Person(name=王五, age=45, salary=6439.23)
Person(name=钱六, age=28, salary=9333.4)
归约
归约指reduce操作,将集合中的元素合并为一个最后结果
//reduce(T identity,BinaryOperator) 可以将流中元素反复结合起来,得到一个值。返回T
//初始值为1,求1~10的和
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer reduce = list.stream().reduce(1,(i1,i2) -> i1+i2);
System.out.println(reduce);
//reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个值,返回Optional<T>
//求1~10的和
Optional<Integer> reduce1 = list.stream().reduce((i1, i2) -> i1 + i2);
System.out.println(reduce1);
运行结果如下:
56
Optional[55]
收集
collectct(Collector c)将流转化为其他的形式,常常是列表或集合。接收一个Collector接口的实现,用于装载元素
用法如下:
//准备数据
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三",18,3143.20));
list.add(new Person("李四",32,10941.43));
list.add(new Person("王五",45,6439.23));
list.add(new Person("钱六",28,9333.40));
//集合汇总
List<Person> collect = list.stream().filter(p -> p.getSalary() > 6000).collect(Collectors.toList());
collect.forEach(System.out::println);
可以看到collect之后是一个list,如果想要set,将collect参数改为Collectors.toSet()即可