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();
Stream<Student> stream = list.stream();
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
| 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();
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
| 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
| 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().skip(30).forEach(System.out::println);
|
执行此代码无任何输出,因为此时生成的流为空
去重distinct
去重,根据stream中元素自己的hashcode()和equals()进行判断,效果可以类比sql中的DISTINCT
1 2 3 4
| 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 = list.stream(); Stream<String> limit = stream.map(Student::getName).limit(3); limit.forEach(System.out::println);
|
输出信息为:
可以看到经过了map,原本的Student流最终映射为String流
再举个例子:
**格式转换:**截取流中前三个元素的姓名和年龄,产生一个新的字符串,格式为姓名:年龄
,以次产生一个新的流
1 2 3 4
| Stream<Student> stream1 = list.stream(); Stream<String> limit1 = stream1.map(student -> student.getName() + " : " + student.getAge()).limit(3); limit1.forEach(System.out::println);
|
打印结果如下:
可以看到了,通过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);
|
输出结果:
map与flatmap的区别
可能你觉得map与flatmap好像没啥区别,都是在映射,都是把一个流变成另一个流
但其实大有不同!!!
- 注意他们的参数Function
- map是对每个元素进行映射,把所有映射后元素转为一个新的流
- flatmap是对每个元素进行映射后,每个元素都转变成一个流,最终把产生的多个流整合为一个流
先看两个Api的参数
1 2 3 4 5
| <R> Stream<R> map(Function<? super T, ? extends R> mapper);
<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
| IntStream sorted = Arrays.stream(new int[]{1, 20, 3, 99,11}).sorted(); sorted.forEach(System.out::println);
|
输出结果
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}
|