Java8特性2 - StreamApi

Stream

  • Stream API 关注对数据的运算,属于CPU密集型
  • Collections 关注对数据的存储,属于IO密集型

Stream 自己本身不存储元素
Stream 不会改变元对象,但是他会返回一个持有结果的新Stream
Stream 操作是延时执行的,意味着需要结果时才执行

Stream执行流程

1
执行流程: 实例化 ==> 中间操作 ==> 终止操作

中间操作往往是一个操作链
一旦终止操作,就开始执行中间操作链,并产生结果。【延时执行,终止操作触发执行】

准备数据

为了测试方便,这里写一个学生工具类StudentData.java,用来获取测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class StudentData {
public static List<Student> getList(){
List<Student> list = new ArrayList<>();
list.add(new Student(1001,"张三",34,50000));
list.add(new Student(1002,"李四",19,3000));
list.add(new Student(1003,"王五",14,600));
list.add(new Student(1004,"赵六",42,30000));
list.add(new Student(1005,"李明",22,6000));
list.add(new Student(1006,"张华",32,40000));
list.add(new Student(1007,"李华",30,9000));
list.add(new Student(1008,"王二",28,12000));
return list;
}
}

class Student{
private Integer id;
private String name;
private Integer age;
private float salary;

public Student(Integer id, String name, Integer age, float salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}

public Integer getId() {
return id;
}

public Student setId(Integer id) {
this.id = id;
return this;
}

public String getName() {
return name;
}

public Student setName(String name) {
this.name = name;
return this;
}

public Integer getAge() {
return age;
}

public Student setAge(Integer age) {
this.age = age;
return this;
}

public float getSalary() {
return salary;
}

public Student setSalary(float salary) {
this.salary = salary;
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Float.compare(student.salary, salary) == 0 &&
Objects.equals(id, student.id) &&
Objects.equals(name, student.name) &&
Objects.equals(age, student.age);
}

@Override
public int hashCode() {
return Objects.hash(id, name, age, salary);
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}

Stream实例化

  • 通过集合
  • 通过数组
  • Stream.of()
  • Stream.iterate() 迭代创建
  • Stream.generate() 生成创建

通过集合创建流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<Student> list = StudentData.getList();
/**
* 创建一个顺序流(按流的顺序进行中间操作)
* default Stream<E> stream()
* {@link Collection#stream()}
*/
Stream<Student> stream = list.stream();



/**
* 创建一个并行流(并行进行中间操作,无顺序)
* default Stream<E> parallelStream()
* {@link Collection#parallelStream()}
*/
Stream<Student> studentStream = list.parallelStream();

通过数组创建流

1
2
3
4
5
int[] arr = new int[]{1,2,3,4,5};
IntStream stream = Arrays.stream(arr);

Student[] students = {new Student(1,"zhang3",15,2000),new Student(2,"li4",25,3000)};
Stream<Student> stream1 = Arrays.stream(students);

通过Stream.of(T t)创建流

1
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

通过Stream.generate()生成流

1
2
//生成十个随机数
Stream.generate(Math::random).limit(10).forEach(System.out::println);

为了展示效果,这里用到了终止操作forEach(Consumer c),来打印生成的流,输出如下:

1
2
3
4
5
6
7
8
9
10
0.4110376914730558
0.3859646598602653
0.6615549365050744
0.5086477303367989
0.2614939389108638
0.4766495481509283
0.4378851389809656
0.018579677210072254
0.5217833438932207
0.44390638190496046

通过Stream.iterate()创建流

1
2
//创建前10个偶数
Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println);

输出如下:

1
2
3
4
5
6
7
8
9
10
0
2
4
6
8
10
12
14
16
18

Stream中间操作

  • 1、筛选与切片 filter、limit、skip、distinct
  • 2、映射 map、mapToDouble、mapToInt、mapToLong、flatMap
  • 3、排序 sorted()、sorted(Comparator c)

筛选与切片

筛选流filter

从流中筛选需要的元素

1
2
3
4
List<Student> list = StudentData.getList();
Stream<Student> stream = list.stream();
//filter 筛选出年龄大于40的学生
stream.filter(s -> s.getAge() > 40).forEach(System.out::println);

输出结果如下:

1
Student{id=1004, name='赵六', age=42, salary=30000.0}

截断流limit

从stream中获取指定大小的stream,可以类比sql中的LIMIT

1
2
//limit 截断流
list.stream().limit(4).forEach(System.out::println);

输出如下:

1
2
3
Student{id=1001, name='张三', age=34, salary=50000.0}
Student{id=1002, name='李四', age=19, salary=3000.0}
Student{id=1003, name='王五', age=14, salary=600.0}

跳过元素skip

从stream中跳过指定个数后获取stream

1
2
//skip 跳过2个元素后截取1个元素
list.stream().skip(2).limit(1).forEach(System.out::println);

输出结果如下:

1
Student{id=1003, name='王五', age=14, salary=600.0}

**注意:**当跳过的个数超过stream中元素个数,返回空流

1
2
/*此时的list.stream()中只有8个元素,直接跳过30个元素*/
list.stream().skip(30).forEach(System.out::println);

执行此代码无任何输出,因为此时生成的流为空

去重distinct

去重,根据stream中元素自己的hashcode()和equals()进行判断,效果可以类比sql中的DISTINCT

1
2
3
4
//distinct 可以看到list中有两个Tony老师,出现重复
list.add(new Student(1009, "Tony", 18, 50000));
list.add(new Student(1009, "Tony", 18, 50000));
list.stream().distinct().forEach(System.out::println);

输出结果

1
2
3
4
5
6
7
8
9
Student{id=1001, name='张三', age=34, salary=50000.0}
Student{id=1002, name='李四', age=19, salary=3000.0}
Student{id=1003, name='王五', age=14, salary=600.0}
Student{id=1004, name='赵六', age=42, salary=30000.0}
Student{id=1005, name='李明', age=22, salary=6000.0}
Student{id=1006, name='张华', age=32, salary=40000.0}
Student{id=1007, name='李华', age=30, salary=9000.0}
Student{id=1008, name='王二', age=28, salary=12000.0}
Student{id=1009, name='Tony', age=18, salary=50000.0}

去重后仅保留一个Tony对象

映射

映射就是 a -> b 的过程,比如把水放进冰箱一段时间就会变成冰块,把水果放进榨汁机榨汁就会变成果汁等等,这些都是映射,只不过他们的映射规则不同。

在Stream中映射有两种

  • map(Function f)
  • flatMap(Function f)

map(Function f)

1
2
将一个流中元素转换成其他形式,或者提取其中信息,最终产生一个新的流
其中这个Function就是映射规则,该函数会被应用到流中每一个元素上,并将其映射成一个新的元素

举个例子说明下:

**信息提取:**提取流中前3个元素的姓名属性, 映射成新的元素,最终生成一个新的流,

为了好理解,这里用了终止操作forEach(),并将Stream实例化、中间操作、终止操作分开写。

1
2
3
4
//Stream<Student> --------> Stream<String>
Stream<Student> stream = list.stream();
Stream<String> limit = stream.map(Student::getName).limit(3);
limit.forEach(System.out::println);

输出信息为:

1
2
3
张三
李四
王五

可以看到经过了map,原本的Student流最终映射为String流

再举个例子:

**格式转换:**截取流中前三个元素的姓名和年龄,产生一个新的字符串,格式为姓名:年龄,以次产生一个新的流

1
2
3
4
//Stream<Student> --------> Stream<String>
Stream<Student> stream1 = list.stream();
Stream<String> limit1 = stream1.map(student -> student.getName() + " : " + student.getAge()).limit(3);
limit1.forEach(System.out::println);

打印结果如下:

1
2
3
张三 : 34
李四 : 19
王五 : 14

可以看到了,通过map,将Student流映射成指定格式的String流

FlatMap(Function f)

1
接收一个函数作为映射规则,该函数把流中的每个元素都转换成一个新的流,最后再把这些流连接成一个流

说人话就是,flatmap会把每一个元素都映射成一个流,最终把多个流整合成一个流

举个例子

将这个字符串数组创建的Stream中的每个元素都用,分割后生成一个流,最终整合为一个完整的流

1
2
String[] strings = {"a","b,c,d","A,B"};
Stream.of(strings).flatMap((s)->Stream.of(s.split(","))).forEach(System.out::println);

输出结果:

1
2
3
4
5
6
a
b
c
d
A
B

map与flatmap的区别

可能你觉得map与flatmap好像没啥区别,都是在映射,都是把一个流变成另一个流

但其实大有不同!!!

  • 注意他们的参数Function
  • map是对每个元素进行映射,把所有映射后元素转为一个新的流
  • flatmap是对每个元素进行映射后,每个元素都转变成一个流,最终把产生的多个流整合为一个流

先看两个Api的参数

1
2
3
4
5
//map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

//flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

很明显flatMap的返回值R是Stream类型,这正对应了之前说的flatMap会把每个元素转换成Stream

如果把上一个例子中的flatmap换成map,试试会如何

1
2
String[] strings = {"a","b,c,d","A,B"};
Stream.of(strings).map((s)->Stream.of(s.split(","))).forEach(System.out::println);

输出结果:

1
2
3
java.util.stream.ReferencePipeline$Head@1963006a
java.util.stream.ReferencePipeline$Head@7fbe847c
java.util.stream.ReferencePipeline$Head@41975e01

打印了3个对象,说明使用了map之后,最终生成的流中的3个元素都是流,并没有像flatmap进行整合操作

简单的总结下就如下:

1
2
map: 1个流 ----> 1个流
flatmap: 1个流 ----> n个流 ----> 1个流

排序

Stream中间操作中有两种排序

  • sorted()
  • sorted(Comparator c)

sorted()自然排序

1
2
3
//sorted() 自然排序
IntStream sorted = Arrays.stream(new int[]{1, 20, 3, 99,11}).sorted();
sorted.forEach(System.out::println);

输出结果

1
2
3
4
5
1
3
11
20
99

sorted(Comparator c)

自定义排序,参数即为排序规则

1
2
3
4
5
//根据对象中的年龄属性排序
List<Student> list = StudentData.getList();
list.stream()
.sorted((s1,s2)>Integer.compare(s1.getAge(),s2.getAge()))
.limit(3).forEach(System.out::println);

输出结果

1
2
3
Student{id=1003, name='王五', age=14, salary=600.0}
Student{id=1002, name='李四', age=19, salary=3000.0}
Student{id=1005, name='李明', age=22, salary=6000.0}
---------- 😏本文结束  感谢您的阅读😏 ----------
评论