Java 并发编程 -- Stream API

简历

Java 8 的新特性:Stream(中文称之为:流)也是很常见且常用的,主要优点是提升开发效率,使代码更干净、简洁。

Stream 的主要作用是对集合(Collection)中的数据进行各种操作,增强了集合对象的功能。

流迭代

在 Java 中,Stream 是一个接口(interface),当然也有多个实现类。但 Java 是面向接口编程的,我们暂时不用关心具体有哪些实现类,先学会使用 Stream API

在接口中提供了操作数据的方法,通常我们叫作 API

创建流

创建的流的方式常见的有多种:

一、直接创建

import java.util.stream.Stream;

Stream<String> stream = Stream.of("苹果", "哈密瓜", "香蕉", "西瓜", "火龙果");

二、由数组转化

String[] fruitArray = new String[] {"苹果", "哈密瓜", "香蕉", "西瓜", "火龙果"};
Stream<String> stream = Stream.of(fruitArray);

 三、由集合转化

List<String> fruits = new ArrayList<>();
fruits.add("苹果");
fruits.add("哈密瓜");
fruits.add("香蕉");
fruits.add("西瓜");
fruits.add("火龙果");
Stream<String> stream = fruits.stream();

 无论是哪种方式创建,一定要清楚的知道:由于源数据(集合或数组)的数据是有序的,所以流中的元素也是有序排队的。

迭代流

集合类提供了 forEach() 方法可以遍历集合中的元素。很巧的是,Stream 提供的迭代方法也叫做 forEach()

Stream<String> stream = Stream.of("苹果", "哈密瓜", "香蕉", "西瓜", "火龙果");
stream.forEach(System.out::println);

 注意:为了能让系统自动识别 Lambda 表达式的参数类型,也必须使用泛型语法指定 Stream 中对象的类型。

Stream API 有很多,功能十分强大,迭代流只是最基础的应用

注意:

集合类的 forEach() 方法与流的 forEach() 方法无关,仅仅是方法名相同而已。不要混淆哦。

流数据过滤

例:

湖北省襄阳市红旗区向阳小学正在评选优秀学生,要求是期末考试平均分不低于 80 分且违规记录,才有资格参与评选。

小学生的模型是:

public class Pupil {
    private String name;
    // 平均分
    private int averageScore;
    // 违规次数
    private int violationCount;
}
name平均分违规次数
司音751
白浅800
荀飞盏958
墨渊790
夜华900
霓漫天810

 问题

如何统计哪些小学生有参与评选的资格呢?

传统实现

一般的代码是这样的:

List<Pupil> pupils = new ArrayList<>();
// 这里假设小学生数据对象已经存入了

// 有资格的小学生集合
List<Pupil> qualified = new ArrayList<>();
for (Pupil pupil : pupils) {
    // 统计是否满足条件
    if (pupil.getAverageScore() >= 80 && pupil.getViolationCount() < 1) {
        qualified.add(pupil);
    }
}

// 打印符合条件的小学生姓名
for (Pupil pupil : qualified) {
    System.out.println(pupil.getName());
}

新特性实现

用 Java8 新特性完成统计:

List<Pupil> pupils = new ArrayList<>();
// 这里假设小学生数据对象已经存入了

pupils.stream()
    .filter(pupil -> pupil.getAverageScore() >= 80 && pupil.getViolationCount() < 1)
    .forEach(pupil -> {System.out.println(pupil.getName());});

 filter() 方法

从方法名我们可以理解其功能:对流中的数据对象进行过滤。 

 方法参数是一个 Lambda 表达式,箭头后面是条件语句,判断数据需要 符合 的条件。

也就是说,使用 Lambda 表达式告诉过滤器,需要那些 符合 条件的数据(把不符合条件的数据过滤掉)。

注意:这里的 Lambda 表达式略有不同。

箭头后的过滤条件语句(非可执行的语句)。传统代码中,条件语句写在 () 中的,所以新特性条件语句可以用 () 而不能用 {} 哦。

等同于 pupil -> ((pupil.getAverageScore() >= 80) && (pupil.getViolationCount() < 1))

流的设计思想(一)

数据流的操作过程,可以看做一个管道,管道由多个节点组成,每个节点完成一个操作。

数据流输入这个管道,按照 顺序 经过各个节点。

 流​数据映射

小习题:对于一组数字:

List<Integer> numbers = Arrays.asList(3, 2, 2, 7, 63, 2, 3, 5);

计算每个数字的平方数并输出。 

解答

使用 Stream API 就很简便了:

numbers.stream()
    .map(num -> {
        return num * num;
    })
    .forEach(System.out::println);

 这里用到的,是 map() 方法。

 map() 方法通常称作映射,其作用就是用新的元素替换掉流中原来相同位置的元素。相当于每个对象都经历一次转换。

映射到新数据

map() 方法的参数是一个 Lambda 表达式,在语句块中对流中的每个数据对象进行计算、处理,最后用 return 语句返回的对象,就是转换后的对象。

优点

映射后的对象类型,可以与流中原始的对象类型不一致。

在流中,可以用字符串替换原来的整数。这就极大的提供了灵活性扩展性,让流的后继操作可以更方便。

特例写法

少数情况下,如果替换语句简单、系统能自动识别需要返回的值,代码可以简写为:

.map(num -> num * num)

 流数据排序

对于数据的处理,排序是很常见的操作。在上一章第二节课中,我们学会了使用 Lambda 表达式优化语句:

List<Student> students = new ArrayList<Student>();
students.add(new Student(111, "bbbb", "london"));
students.add(new Student(131, "aaaa", "nyc"));
students.add(new Student(121, "cccc", "jaipur"));

// 实现升序排序
Collections.sort(students, (student1, student2) -> {
  // 第一个参数的学号 > 第二个参数的学号
  return student1.getRollNo() - student2.getRollNo();
});

students.forEach(s -> System.out.println(s));

 使用 Stream API 就更简便了:

students.stream()
    // 实现升序排序
    .sorted((student1, student2) -> {
        return student1.getRollNo() - student2.getRollNo();
    })
    .forEach(System.out::println);

 sorted() 顾名思义,就是完成排序的方法。把排序规则写成一个 Lambda 表达式传给此方法即可。

参数顺序

无论是 Lambda 表达式 写法,还是 Stream API 写法,参数 (student1, student2) 中的 student1 和 student2 分别指代第几个元素呢?

答案是:student1 指代后一个元素,student2 指代前一个元素。不要被变量名迷惑了哦。

第一次遍历时,student1 指代第二个名称为 aaaa 的学生,student2 指代第一个名称为 bbbb 的学生。

特例写法

少数情况下,如果排序计算语句简单、系统能自动识别需要返回的值,代码可以简写为:

.sorted((student1, student2) -> student1.getRollNo() - student2.getRollNo())

 小知识点

不知道大家总结过没有,为什么无论是集合排序 Collections.sort() 还是流排序 sorted(),都需要返回一个数值呢?返回数值跟排序升降有啥关系呢?

本节课案例中,前两个学生的学号相减 131-111 = 20 ,为什么返回 20 就表示升序呢?

因为,返回 非正数 表示两个相比较的元素需要 交换位置 ,返回正数则不需要。

131-111 = 20 表示前两个学生不需要改变在集合中的位置顺序。

那么后两个学生进行比较:

121-131 = -10 返回了负数,于是名称为 aaaa 的学生需要与名称为 cccc 的学生交换位置、改变顺序,完成了由小到大的升序排序。

那么如何进行降序排序呢?

同理 使用 student2.getRollNo() - student1.getRollNo() 。

集合中前两个学生 111-131=-20 ,此时返回的结果为非正数,所以两者交换位置

后两个学生进行比较 111-121=-10 返回了负数,所以两者交换顺序,最终变成了 131->121->111 的集合顺序,完成降序排序。

流数据摘取

小习题:对于一组数字:

List<Integer> numbers = Arrays.asList(3, 2, 2, 7, 63, 2, 3, 5);

找出最大的前 3 个数字。 

答案

我们马上可以想到使用排序就能知道哪个数字最大。

再此基础上,Stream API 提供了摘取接口,很容易完成:

numbers.stream()
    .sorted((n1, n2) -> n2 - n1)
    .limit(3)
    .forEach(System.out::println);

limit() 方法的作用是返回流的  n 个元素,当然 n 不能为负数。 

不是摘取任意位置哦,只能是流开头的

流的设计思想(二)

对比普通 Java 代码,Stream 的显著特点是:编程的重点,不再是对象的运用,而是数据的计算。

如果使用普通的 Java 代码,重点是使用对象完成各种各样的逻辑,加上语法代码也比较多,导致整个代码比较繁复。

使用了 Stream API,系统会自动完成很多操作,加上大幅度简化了语法,开发者的注意力重点就变为捋清楚数据计算的步骤,不用太关心变量类型、变量赋值、对象转换等。编程的重点更加清晰。

Stream 的这种变化,特征是:函数式风格。即弱化了面向对象的严格、完整的语法,重心变为通过函数完成数据计算。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DF_Orange

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值