【Java 教程】一文带你全方位弄懂 Java8 流式操作!

什么是流式操作

Java 8 API 添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API 可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

1. 流式操作举例

1.1 创建实体类
 1public class Person {
 2    private String name;
 3    private Integer age;
 4    private Integer score;
 5
 6    public String getName() {
 7        return name;
 8    }
 9
10    public void setName(String name) {
11        this.name = name;
12    }
13
14    public Integer getAge() {
15        return age;
16    }
17
18    public void setAge(Integer age) {
19        this.age = age;
20    }
21
22    public Integer getScore() {
23        return score;
24    }
25
26    public void setScore(Integer score) {
27        this.score = score;
28    }
29
30    public Person() {
31    }
32
33    public Person(String name, Integer age, Integer score) {
34        this.name = name;
35        this.age = age;
36        this.score = score;
37    }
38
39    @Override
40    public String toString() {
41        return "Person{" +
42                "name='" + name + '\'' +
43                ", age=" + age +
44                ", score=" + score +
45                '}';
46    }
47}
1.2 传统的对象初始化方式
1public class Program {
 2    public static void main(String[] args) {
 3        //使用构造器设置对象信息
 4//        Person xiaomign = new Person("小明", 28, 90);
 5
 6        //使用getter、setter方式设置对象信息
 7        Person xiaoming = new Person();
 8        xiaoming.setName("小明");
 9        xiaoming.setAge(18);
10        xiaoming.setScore(90);
11    }
12}
1.3 使用流式操作初始化对象
1.3.1 修改实体类
 1public class Person {
 2    private String name;
 3    private Integer age;
 4    private Integer score;
 5
 6    public String getName() {
 7        return name;
 8    }
 9
10    public Person setName(String name) {
11        this.name = name;
12        return this;
13    }
14
15    public Integer getAge() {
16        return age;
17    }
18
19    public Person setAge(Integer age) {
20        this.age = age;
21        return this;
22    }
23
24    public Integer getScore() {
25        return score;
26    }
27
28    public Person setScore(Integer score) {
29        this.score = score;
30        return this;
31    }
32
33    public Person() {
34    }
35
36    public Person(String name, Integer age, Integer score) {
37        this.name = name;
38        this.age = age;
39        this.score = score;
40    }
41
42    @Override
43    public String toString() {
44        return "Person{" +
45                "name='" + name + '\'' +
46                ", age=" + age +
47                ", score=" + score +
48                '}';
49    }
50}
1.3.2 使用流式操作
1//流式操作
2xiaoming.setName("小明").setAge(20).setScore(100);

2. 集合的流式操作

集合的流式操作是 Java8 的一个新特性,流式操作不是一个数据结构,不负责任何的数据存储,它更像是一个迭代器,可以有序的获取数据源中的每一个数据,并且可以对这些数据进行一些操作。流式操作的每一个方法的返回值都是这个流的本身。

2.1 流式操作的三个步骤
2.1.1 获取数据源:集合、数组

设置数据源:

 1public class Data {
 2    /**
 3     * 数据源
 4     */
 5    public static ArrayList<Person> getData() {
 6        ArrayList<Person> list = new ArrayList<Person>();
 7
 8        list.add(new Person("小明", 18, 100));
 9        list.add(new Person("小丽", 19, 70));
10        list.add(new Person("小王", 22, 85));
11        list.add(new Person("小张", 20, 90));
12        list.add(new Person("小黑", 21, 95));
13        return list;
14    }
15}

获取数据源的方式:

 1public class Program {
 2    public static void main(String[] args) {
 3
 4        // 获取数据源方式1
 5        Stream stream = Data.getData().stream();
 6
 7        // 获取数据源方式2
 8        Stream.of(Data.getData());
 9
10        // 获取数据源方式3
11            //数据源为数组
12    }
13}
2.1.2 对数据进行处理的过程:过滤、排序、映射等(中间操作)
中间操作1:filter。使用filter自定义条件过滤数据:
1// 中间操作1: filter
2// filter是一个过滤器,可以自定义一个过滤条件,将流中满足条件的元素保留
3// 查找集合中成绩小于80的学生
4List<Person> list = Data.getData().stream()
5    .filter(ele -> ele.getScore() < 80)
6    .collect(Collectors.toList());
7System.out.println(list);

image

中间操作2:distinct。使用 distinct 实现去重操作,在数据源中添加重复的数据:
1list.add(new Person("小黑", 21, 95));    //此时list中有两个小黑

在实体类中重写 hashCode() 和 equals() 方法:

 1@Override
 2public boolean equals(Object o) {
 3    if (this == o) return true;
 4    if (o == null || getClass() != o.getClass()) return false;
 5    Person person = (Person) o;
 6    return Objects.equals(name, person.name) &&
 7        Objects.equals(age, person.age) &&
 8        Objects.equals(score, person.score);
 9}
10
11@Override
12public int hashCode() {
13    return Objects.hash(name, age, score);
14}

去重规则,先判断对象的 hashCode(),如果 hashCode() 相同再判断 equals():

1// 中间操作2: distinct
2// distinct: 取出集合中不同的元素
3// 去重规则:
4// 1.先判断对象的hashCode()
5// 2.如果hashCode()相同再判断equals()
6Data.getData().stream().distinct().forEach(System.out::println);

image

注意:如果小黑的数据相同却要保存两份,可以在 hashCode() 方法中返回一个随机数,随机数很小概率会相同,为了确保稳定性,可以将 equals() 方法改为返回 false,这样可以保留两个信息相同的小黑。

中间操作3:sorted。使用 sorted() 方法以成绩进行升序排序,要求实体类实现 Comparable 接口并重写方法:
1// 中间操作3: sorted
2// sorted: 对返回的元素进行排序
3// sorted(): 要求实体类实现Comparable接口并重写方法
4Data.getData().stream().sorted().forEach(System.out::println);

image

中间操作4:limit。在数据源中取前三个数据:
1// 中间操作4: limit
2// limit: 限制,只取流中前指定位的数据
3// 在数据源中取前三个数据
4Data.getData().stream().limit(3).forEach(System.out::println);

image

中间操作5:skip。跳过前三个元素,取后面剩下的元素:
1// 中间操作5: skip
2// skip: 跳过
3// 跳过前三个元素,取后面剩下的元素
4Data.getData().stream().skip(3).forEach(System.out::println);

image

中间操作6:map。元素映射,用指定的元素替换掉流中的元素,使用 map 将对象替换为对象的名字:
1// 中间操作6: map
2// map: 元素映射,用指定的元素替换掉流中的元素
3// 将流中的Person对象替换位他们的姓名
4Data.getData().stream().map(ele -> ele.getName()).forEach(System.out::println);

image

2.1.3 对流中数据的整合:转成集合、数量(最终操作)
最终操作1:collect。转换为List:
 1public class Program {
 2    public static void main(String[] args) {
 3
 4        // 获取数据源方式1
 5        Stream<Person> stream = Data.getData().stream();
 6
 7        // 最终操作1: collect,配合Collectors使用
 8        // 将集合中的元素转换成List
 9        List<Person> list = stream.collect(Collectors.toList());
10
11        System.out.println(list);
12    }
13}

image

转换为 set:

1// 将集合中的元素转换为Set
2Set<Person> set = stream.collect(Collectors.toSet());
3System.out.println(set);

image

转换为 map:

 1// 转换为Map(name为键,score为值)
 2// 方式1
 3// Map<String, Integer> map = stream.collect(Collectors.toMap(
 4//      ele -> ele.getName(),
 5//      ele -> ele.getScore()
 6// ));  
 7
 8// 方式2        
 9Map<String, Integer> map = stream.collect(Collectors.toMap(
10    Person::getName,
11    Person::getScore
12));

image

最终操作2:reduce。reduce 的思想,比如在计算一个数组中的元素的和时,首先会计算前两个数的和,然后拿着前两个数的和与第三个数求和,计算出结果后将三个数的和与第四个数相加,以此类推。

image

计算数组中数据的和:

1// 最终操作2: reduce(将数据汇总在一起)
2Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
3Optional<Integer> res = stream1.reduce((n1, n2) -> n1 + n2);
4// 获取到最终的返回值
5System.out.println(res.get());

image

使用 reduce 计算 Person 对象中成绩的和:

1// 计算Person中Score的和
2Optional<Person> res = stream.reduce(
3    (n1, n2) -> new Person().setScore(n1.getScore() + n2.getScore())
4);
5System.out.println(res.get().getScore());

image

缺点:上面的写法每次都会产生一个临时的对象,产生了不必要的性能损耗。

使用 reduce 计算 Person 对象中成绩的和(优化):

1// 计算Person中Score的和(使用临时变量,减少性能开销)
2Person temp = new Person();
3Optional<Person> res = stream.reduce(
4    (n1, n2) -> temp.setScore(n1.getScore() + n2.getScore())
5);
6System.out.println(res.get().getScore());

image

最终操作3:max 和 min。使用 max 找出 Person 中成绩最高的人:
1// 最终操作3: max和min
2// 需求1: 找到集合中成绩最高的人的信息
3Person max = stream.max(
4    (ele1, ele2) -> ele1.getScore() - ele2.getScore()
5).get();
6System.out.println(max);

image

使用 min 找出 Person 中成绩最低的人:

1// 需求2: 找到集合中成绩最低的人的信息
2Person min = stream.min(
3    (ele1, ele2) -> ele1.getScore() - ele2.getScore()
4).get();
5System.out.println(min);

image

最终操作 4:matching。使用 anyMatch 查看集合中是否有成绩高于 80 的人:
1// 判断集合中是否包含成绩大于80的学员
2boolean res1 = stream.anyMatch((ele) -> ele.getScore() > 80);
3System.out.println(res1);

image

使用 allMatch 查看集合中的成绩是否全部高于 60:

1//查看集合中的人的成绩是否全部高于60
2boolean res2 = stream.allMatch((ele) -> ele.getScore() > 60);
3System.out.println(res2);

image

使用 noneMatch 查看集合中的人的分数是否不包含 80 以下的:

1boolean res3 = stream.noneMatch((ele) -> ele.getScore() < 80);
2System.out.println(res3);

image

最终操作 5:count。使用 count 计算元数据中有多少条数据:
1// 最终操作5: 求元数据中有多少个元素
2long count = stream.count();
3System.out.println(count);

image

最终操作 6:forEach。使用 forEach 遍历集合中的元素:
1// 最终操作6: forEach
2// stream.forEach(ele -> System.out.println(ele));
3stream.forEach(System.out::println);

image

最终操作7:findFirst 和 findAny。
FindFirst:获取流中的第一个元素。FindAny:获取流中任意一个元素(并不是随机获取元素) 对于串行流,结果等同于 findFirst。findAny 用于并行流中可能会与 findFirst 一样,也可能不一样:
1// FindFirst: 获取流中的第一个元素
2// FindAny: 获取流中任意一个元素(并不是随机获取元素)
3//          对于串行流,结果等同于findFirst
4//          findAny用于并行流中可能会与findFirst一样,也可能不一样
5System.out.println(Data.getData().parallelStream().findFirst());
6System.out.println(Data.getData().stream().findFirst());
7System.out.println(Data.getData().parallelStream().findAny());
8System.out.println(Data.getData().stream().findAny());
最终操作的注意事项,为什么会被称为最终操作?
1Person max = stream.max(
2    (ele1, ele2) -> ele1.getScore() - ele2.getScore()
3).get();
4Person min = stream.min(
5    (ele1, ele2) -> ele1.getScore() - ele2.getScore()
6).get();

image

报错信息表示流正在被处理或者已经被关闭了,如果已经被关闭了再次调用当然会报错,这也是为什么叫最终操作的原因。

3. 并行流

3.1 获取并行流的方式
1// 并行流
2// 获取并行流的两种方式
3Data.getData().stream().parallel();
4Data.getData().parallelStream();
3.2 并行流与串行流对比
 1// 串行流: 19920ms
 2// 并行流: 12204ms
 3long startTime = System.currentTimeMillis();
 4//LongStream.rangeClosed(0L, 50000000000L)
 5//    .reduce(Long::sum);
 6LongStream.rangeClosed(0L, 50000000000L)
 7    .parallel()
 8    .reduce(Long::sum);
 9long endTime = System.currentTimeMillis();
10
11System.out.println(endTime - startTime);
3.3 flatMap
1String[] array = {"hello", "world"};
2// 需要获取所有字符 List -> h, e, l, l, o, w, o, r, l, d
3//        Arrays.stream(array)
4//                .map(ele -> ele.split(""))
5//                .forEach(ele -> System.out.println(ele.length));
6System.out.println(Arrays.stream(array)
7                   .map(ele -> ele.split(""))
8                   .flatMap(Arrays::stream)
9                   .collect(Collectors.toList()));

image

4. Collectors

Collectors 是一个工具类,提供着若干个方法,返回一个 Collector 接口的实现类对象。

4.1 maxBy

通过指定的规则获取流中最大的元素:

1System.out.println(Data.getData().stream()
2       collect(Collectors.maxBy((ele1, ele2) ->
3              ele1.getScore() - ele2.getScore())));

image

4.2 minBy

通过指定的规则获取流中最小的元素:

1System.out.println(Data.getData().stream()
2                .collect(Collectors.minBy((ele1, ele2) ->
3                 ele1.getScore() - ele2.getScore())));

image

4.3 joining

合并,将流中的元素,以字符串的形式拼接起来:

1// 把Person中的姓名拼成一个字符串
2String res1 = Data.getData().stream()
3    .map(Person::getName)
4    .collect(Collectors.joining());
5System.out.println(res1);

image

1String res2 = Data.getData().stream()
2    .map(Person::getName)
3    .collect(Collectors.joining("-"));
4System.out.println(res2);

image

1String res3 = Data.getData().stream()
2    .map(Person::getName)
3    .collect(Collectors.joining("-", "{", "}"));
4System.out.println(res3);

image

4.4 summingInt

计算 int 类型的和,将流中的元素映射为 int 类型的元素进行求和。将 Person 对象的成绩进行求和:

1// 将Person对象的成绩进行求和
2System.out.println(Data.getData().stream()
3                   .collect(Collectors.summingInt(ele ->
4                    ele.getScore())));

image

4.5 averagingInt

计算 int 类型的平均值,计算不及格学生的平均成绩:

1System.out.println(Data.getData().stream()
2                   .filter(ele -> ele.getScore() < 60)
3                   .collect(Collectors.averagingInt(Person::getScore)));
4.6 summarizingInt

将流中的元素映射成 int 类型的元素,获取这些数据的描述信息:

1System.out.println(Data.getData().stream()
2                   .collect(Collectors.summarizingInt(ele -> ele.getScore())));

image

服务推荐

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值