import pojo.Student;
import java.lang.reflect.Array;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class stream_learn {
// 流式编程是为了方便处理集合,节省代码量
// 包括 集合的过滤,排序,映射等功能,
// 根据流的操作性 , 氛围
// 串行流,并行流
// 根绝操作返回的结果不同,流式操作又分为
// 中间操作,最终操作。
// 什么是流 ?
// 它不是一个数据结构,只是一个高级的迭代或者遍历
// 内部对集合的处理采用了fork/join模式,jdk7加入的,针对并发处理的框架,
// 这也广泛应用多线程和算法中。
// 常用流操作
// 流主要针对集合相关的操作,所有继承自collection的接口都可以使用流
// default String<E>
// 例程
public static void main(String[] args) {
Integer[] nums = new Integer[]{1, 2, 3, 4, 56};
List<Integer> evens = new ArrayList<>();
for (
final Integer num : nums) {
if (num % 2 == 0) {
evens.add(num);
}
}
// 按照之前的写法,对一个数组数据进行处理并返回符合要求的数组需要多行代码
Stream<Integer> stream = Arrays.stream(nums);
List<Integer> collect = stream.filter(num -> num % 2 == 0).collect(Collectors.toList());
// 解析,
// 首先通过arrays.stream(nums) 转变成流对象,之后调用流式处理的方法
System.out.println(collect);
//jdk 8 的流式处理极大的简化了对于集合的操作,实际上不光是集合,包括数组
// 文件等,只要可以转换成流,都可以借助流式处理,类似于写sql一样对其进行操作
// java8是通过内部迭代来实现对流的处理,一个流式处理可以氛围三部分,
// 转换成流,中间操作,终端操作
// 以集合为例,一个流式处理的操作首先要调用stream()函数将其转换成流
// 然后调用相应的中间操作达到我们需要对集合进行的操作,比如筛选,转换等等
// 最后通过终端操作对前面的返回结果进行封装,返回需要的形式
// 什么是中间操作
// 初始化测试用数据
// 初始化
List<Student> students = new ArrayList<Student>() {
{
add(new Student(20160001, "孔明", 20, 1, "土木工程", "武汉大学"));
add(new Student(20160002, "伯约", 21, 2, "信息安全", "武汉大学"));
add(new Student(20160003, "玄德", 22, 3, "经济管理", "武汉大学"));
add(new Student(20160004, "云长", 21, 2, "信息安全", "武汉大学"));
add(new Student(20161001, "翼德", 21, 2, "机械与自动化", "华中科技大学"));
add(new Student(20161002, "元直", 23, 4, "土木工程", "华中科技大学"));
add(new Student(20161003, "奉孝", 23, 4, "计算机科学", "华中科技大学"));
add(new Student(20162001, "仲谋", 22, 3, "土木工程", "浙江大学"));
add(new Student(20162002, "鲁肃", 23, 4, "计算机科学", "浙江大学"));
add(new Student(20163001, "丁奉", 24, 5, "土木工程", "南京大学"));
}
};
// 过滤,故名肆意就是按照给定的要求对集合进行筛选满足条件的元素,jdk8 提供的筛选操作包括
// filter,distinct,limit,skip
// filter
// 在前面的例子中我们已经演示了如何使用filter,其定义为:Stream<T> filter(Predicate<? super T> predicate),
// filter接受一个谓词Predicate,我们可以通过这个谓词定义筛选条件,在介绍lambda表达式时我们介绍过Predicate是一个函数式接口
List<Student> whuStudents = students.stream()
.filter(student -> "武汉大学".equals(student.getSchool()))
.collect(Collectors.toList());
for (Student s1 :
whuStudents) {
System.out.println(s1.getSchool());
}
// distinct
// distinct操作类似于在编写的sql语句时的 distinct关键字,用于去重处理,distinct给予object.equals 实现,对比的是内存地址,假设希望选出所有不重复的偶数,可以这样写
List<Student> collect1 = students.stream()
.filter(num -> num.getAge() % 2 == 0).distinct()
.collect(Collectors.toList());
System.out.println(collect1.get(1).getAge());
// limit
// limit 操作也类似sql语句中的limit关键字,不过相对功能较弱,limit返回包含前n个元素的流,当集合大小小于n时,则返回实际长度,比如下面的例子返回前两个专业为土木工程专业的学生
List<Student> collect2 = students.stream().filter(student -> "土木工程".equals(student.getMajor())).limit(5).collect(Collectors.toList());
System.out.println(collect2.size());
// 还有另外一个流操作,sorted,该操作用于对流中元素进行排序,sorted要求待比较的元素必须实现comparable接口,如果没有实现也不要紧,可以将比较器作为参数传递给sorted(Comparator
List<Student> sortedCivilStudents = students.stream()
.filter(student -> "土木工程".equals(student.getMajor())).sorted((s1, s2) -> s1.getAge() - s2.getAge())
.limit(2)
.collect(Collectors.toList());
System.out.println(sortedCivilStudents);
// skip
// skip 操作与limit操作相反,如同其字面意思一样,是跳过前n个元素,比如我们希望找出排序在2之后的土木工程专业的学生,可以写为
List<Student> civilStudents = students.stream()
.filter(student -> "土木工程".equals(student.getMajor()))
.skip(2)
.collect(Collectors.toList());
// 映射处理
// 在sql中,借助select 关键字后面添加需要的字段名称,可以仅输出需要的字段数据,而流式处理的映射操作也是实现这一目的,在jdk8的流式处理中,主要包含两类映射操作,map,和flatMap
// map
// 例子
/*
需求背景 : 假设希望筛选出所有专业为计算机科学的学生姓名,那么可以在filter筛选的基础上,通过map将学生实体映射为学生姓名字符串
*/
List<String> collect3 = students.stream().filter(student -> "计算机科学".equals(student.getMajor())).map(Student::getName).collect(Collectors.toList());
System.out.println(collect3);
// 除了上面这类基础的map,java8还提供了mapToDouble(ToDoubleFunction<? super T> mapper),mapToInt(ToIntFunction<? super T> mapper),mapToLong(ToLongFunction<? super T> mapper),
// 这些映射分别返回对应类型的流,java8为这些流设定了一些特殊的操作,比如我们希望计算所有专业为计算机科学学生的年龄之和,那么我们可以实现如下:
long sum = students.stream().filter(student -> "计算机科学".equals(student.getMajor())).mapToInt(Student::getAge).sum();
System.out.println(sum);
// 上面是通过将student按照年龄直接映射为intStream ,可以直接调用提供的sum()方法来达到目的,此外使用这些树之流的好处在于可以避免jvm装箱操作带来的性能损耗
// flatMap
// flatMap与map的区别在于 flatMap是将一个流中的每个值都转换成一个个流,然后再将这些流扁平化称为一个流,距离说明,假设我们有一个字符串数组
// String[] strs= {'java8',"is","easy","to","learn"} 希望输出构成这一组数组的所有非重复自负,可能首先会想到
String[] strs = {"jdk8", "is", "easy", "to", "learn"};
List<String[]> distinctStrs = Arrays.stream(strs)
.map(str -> str.split("")) // 映射成为Stream<String[]>
.distinct()
.collect(Collectors.toList());
System.out.println(distinctStrs);
// 但是执行之后得到的结果是一个包含多个字符串, 构成一个字符串的字符数组 的流,此时执行distinct操作是给予在这些字符串数组之间的对比,所以达不到目的
// dinstinct 还有对一个包含多个字符的客流进行操作的时候才能达到目的,即,对 Stream<String> 进行操作,此时flatMap就可以达到目的
List<String> distinctStrs1 = Arrays.stream(strs)
// 相当于吧 str对象映射成字符串流对象
.map(str -> str.split("")) // 映射成为Stream<String[]>
// 扁平化为 字符流对象
.flatMap(Arrays::stream) // 扁平化为Stream<String>
.distinct()
.collect(Collectors.toList());
/*
flatMap将由map映射得到的Stream<String[]>,转换成由各个字符串数组映射成的流Stream<String>,再将这些小的流扁平化成为一个由所有字符串构成的大流Steam<String>,从而能够达到我们的目的。
与map类似,flatMap也提供了针对特定类型的映射操作:
flatMapToDouble(Function<? super T,? extends DoubleStream> mapper),flatMapToInt(Function<? super T,? extends IntStream> mapper),flatMapToLong(Function<? super T,? extends LongStream> mapper)
*/
// 前述为中间操作
// 终端操作
/*
是流式处理的最后一步,可以在终端操作中实现对流查找,规约等操作
*/
// FIXME 查找
// allMatch
// allMatch用于检测是否全部都满足指定的参数行为,如果全部满足则返回true,例如我们希望检测是否所有的学生都已满18周岁,那么可以实现为:
boolean b = students.stream().allMatch(student -> student.getAge() >= 18);
// anyMatch
// 用来检测是否存在一个或者多个满足制定的参数行为,如果满足则返回true,加入希望检测是否有来自武汉大学的学生,可写
boolean 武汉大学 = students.stream().anyMatch(student -> student.getSchool().equals("武汉大学"));
// noneMatch
// 用于检测是否不存在满足制定行为的元素,如果不存在则返回true,
boolean flag = students.stream().noneMatch(student -> student.getMajor().equals("计算机科学"));
// findFirst 用于返回满足条件的第一个元素,findFirst 不携带参数,,具体查找条件可以使用filter进行设置,此外,findFirst返回的是一个optional类型
// findAny 用于返回任意一个满足过滤条件的元素
// 实际上对于顺序流式处理而言,findFirst和findAny返回的结果是一样的,至于为什么会这样设计,
// 接下来我们介绍的并行流式处理,当我们启用并行流式处理的时候,查找第一个元素往往会有很多限制,如果不是特别需求,在并行流式处理中使用findAny的性能要比findFirst好。
// reduce 归约
// 前面例子中的方法 大多都是通过collect(Collectors.toList()) 对数据进行封装返回,如果我的目标不是返回一个新的集合,
// 而是希望对经过参数化的操作后的集合进一步的运算,可用集合实施规约操作,jdk8 流式处理中提供了reduce方法来达到这一目的
// 前面我们通过mapToInt将Stream<Student>映射成为IntStream,并通过IntStream的sum方法求得所有学生的年龄之和,实际上我们通过归约操作,也可以达到这一目的,实现如下:
int totalAge = students.stream()
.filter(student -> "计算机科学".equals(student.getMajor()))
.mapToInt(Student::getAge).sum();
// 归约操作
int totalAge1 = students.stream()
.filter(student -> "计算机科学".equals(student.getMajor()))
.map(Student::getAge)
.reduce(0, (a, b) -> a + b);
// 进一步简化
int totalAge2 = students.stream()
.filter(student -> "计算机科学".equals(student.getMajor()))
.map(Student::getAge)
.reduce(0, Integer::sum);
// 采用无初始值的重载版本,需要注意返回Optional
Optional<Integer> totalAge3 = students.stream()
.filter(student -> "计算机科学".equals(student.getMajor()))
.map(Student::getAge)
.reduce(Integer::sum); // 去掉初始值
// 收集
// 前面利用collect(Collectors.toList())是一个简单的收集操作,是对处理结果的封装,对应的还有toSet、toMap,以满足我们对于结果组织的需求。这些方法均来自于java.util.stream.Collectors,我们可以称之为收集器。
//
//收集器也提供了相应的归约操作,但是与reduce在内部实现上是有区别的,收集器更加适用于可变容器上的归约操作,这些收集器广义上均基于Collectors.reducing()实现。
// todo 求学生的总人数
// * Returns a {@code Collector} accepting elements of type {@code T} that
// * counts the number of input elements. If no elements are present, the
// * result is 0.
Long collect4 = students.stream().collect(Collectors.counting());
// 进一步简化
long sum2 = students.stream().count();
// 求年龄的最大值,最小值
// 求最大年龄
Optional<Student> olderStudent = students.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()));
// 进一步简化 ,, 由原先的比对逻辑变为
/** comparing
* Accepts a function that extracts a {@link java.lang.Comparable
* Comparable} sort key from a type {@code T}, and returns a {@code
* Comparator<T>} that compares by that sort key.
*
maxby
*/
// * Returns a {@code Collector} that produces the maximal element according
// * to a given {@code Comparator}, described as an {@code Optional<T>}.
// *
//
Optional<Student> olderStudent2 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));
// 求最小年龄
/*
* Returns a {@code Collector} that produces the minimal element according
* to a given {@code Comparator}, described as an {@code Optional<T>}.
*
*/
Optional<Student> olderStudent3 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)));
// 年龄总和
students.stream().collect(Collectors.summingInt(Student::getAge));
// 字符串拼接
students.stream().map(Student::getName).collect(Collectors.joining(","));
// TODO 并行流式处理
// 流式处理中的很多都适合采用 分而治之 的思想,从而在处理集合较大时,极大的提高代码的性能,java8的设计者也看到了这一点,所以提供了 并行流式处理。上面的例子中我们都是调用stream()方法来启动流式处理,java8还提供了parallelStream()来启动并行流式处理,parallelStream()本质上基于java7的Fork-Join框架实现,其默认的线程数为宿主机的内核数。
//
// 启动并行流式处理虽然简单,只需要将stream()替换成parallelStream()即可,但既然是并行,就会涉及到多线程安全问题,所以在启用之前要先确认并行是否值得(并行的效率不一定高于顺序执行),另外就是要保证线程安全。此两项无法保证,那么并行毫无意义,毕竟结果比速度更加重要。
}
}