1. import pojo.Student;
    2. import java.lang.reflect.Array;
    3. import java.util.*;
    4. import java.util.stream.Collectors;
    5. import java.util.stream.Stream;
    6. public class stream_learn {
    7. // 流式编程是为了方便处理集合,节省代码量
    8. // 包括 集合的过滤,排序,映射等功能,
    9. // 根据流的操作性 , 氛围
    10. // 串行流,并行流
    11. // 根绝操作返回的结果不同,流式操作又分为
    12. // 中间操作,最终操作。
    13. // 什么是流 ?
    14. // 它不是一个数据结构,只是一个高级的迭代或者遍历
    15. // 内部对集合的处理采用了fork/join模式,jdk7加入的,针对并发处理的框架,
    16. // 这也广泛应用多线程和算法中。
    17. // 常用流操作
    18. // 流主要针对集合相关的操作,所有继承自collection的接口都可以使用流
    19. // default String<E>
    20. // 例程
    21. public static void main(String[] args) {
    22. Integer[] nums = new Integer[]{1, 2, 3, 4, 56};
    23. List<Integer> evens = new ArrayList<>();
    24. for (
    25. final Integer num : nums) {
    26. if (num % 2 == 0) {
    27. evens.add(num);
    28. }
    29. }
    30. // 按照之前的写法,对一个数组数据进行处理并返回符合要求的数组需要多行代码
    31. Stream<Integer> stream = Arrays.stream(nums);
    32. List<Integer> collect = stream.filter(num -> num % 2 == 0).collect(Collectors.toList());
    33. // 解析,
    34. // 首先通过arrays.stream(nums) 转变成流对象,之后调用流式处理的方法
    35. System.out.println(collect);
    36. //jdk 8 的流式处理极大的简化了对于集合的操作,实际上不光是集合,包括数组
    37. // 文件等,只要可以转换成流,都可以借助流式处理,类似于写sql一样对其进行操作
    38. // java8是通过内部迭代来实现对流的处理,一个流式处理可以氛围三部分,
    39. // 转换成流,中间操作,终端操作
    40. // 以集合为例,一个流式处理的操作首先要调用stream()函数将其转换成流
    41. // 然后调用相应的中间操作达到我们需要对集合进行的操作,比如筛选,转换等等
    42. // 最后通过终端操作对前面的返回结果进行封装,返回需要的形式
    43. // 什么是中间操作
    44. // 初始化测试用数据
    45. // 初始化
    46. List<Student> students = new ArrayList<Student>() {
    47. {
    48. add(new Student(20160001, "孔明", 20, 1, "土木工程", "武汉大学"));
    49. add(new Student(20160002, "伯约", 21, 2, "信息安全", "武汉大学"));
    50. add(new Student(20160003, "玄德", 22, 3, "经济管理", "武汉大学"));
    51. add(new Student(20160004, "云长", 21, 2, "信息安全", "武汉大学"));
    52. add(new Student(20161001, "翼德", 21, 2, "机械与自动化", "华中科技大学"));
    53. add(new Student(20161002, "元直", 23, 4, "土木工程", "华中科技大学"));
    54. add(new Student(20161003, "奉孝", 23, 4, "计算机科学", "华中科技大学"));
    55. add(new Student(20162001, "仲谋", 22, 3, "土木工程", "浙江大学"));
    56. add(new Student(20162002, "鲁肃", 23, 4, "计算机科学", "浙江大学"));
    57. add(new Student(20163001, "丁奉", 24, 5, "土木工程", "南京大学"));
    58. }
    59. };
    60. // 过滤,故名肆意就是按照给定的要求对集合进行筛选满足条件的元素,jdk8 提供的筛选操作包括
    61. // filter,distinct,limit,skip
    62. // filter
    63. // 在前面的例子中我们已经演示了如何使用filter,其定义为:Stream<T> filter(Predicate<? super T> predicate),
    64. // filter接受一个谓词Predicate,我们可以通过这个谓词定义筛选条件,在介绍lambda表达式时我们介绍过Predicate是一个函数式接口
    65. List<Student> whuStudents = students.stream()
    66. .filter(student -> "武汉大学".equals(student.getSchool()))
    67. .collect(Collectors.toList());
    68. for (Student s1 :
    69. whuStudents) {
    70. System.out.println(s1.getSchool());
    71. }
    72. // distinct
    73. // distinct操作类似于在编写的sql语句时的 distinct关键字,用于去重处理,distinct给予object.equals 实现,对比的是内存地址,假设希望选出所有不重复的偶数,可以这样写
    74. List<Student> collect1 = students.stream()
    75. .filter(num -> num.getAge() % 2 == 0).distinct()
    76. .collect(Collectors.toList());
    77. System.out.println(collect1.get(1).getAge());
    78. // limit
    79. // limit 操作也类似sql语句中的limit关键字,不过相对功能较弱,limit返回包含前n个元素的流,当集合大小小于n时,则返回实际长度,比如下面的例子返回前两个专业为土木工程专业的学生
    80. List<Student> collect2 = students.stream().filter(student -> "土木工程".equals(student.getMajor())).limit(5).collect(Collectors.toList());
    81. System.out.println(collect2.size());
    82. // 还有另外一个流操作,sorted,该操作用于对流中元素进行排序,sorted要求待比较的元素必须实现comparable接口,如果没有实现也不要紧,可以将比较器作为参数传递给sorted(Comparator
    83. List<Student> sortedCivilStudents = students.stream()
    84. .filter(student -> "土木工程".equals(student.getMajor())).sorted((s1, s2) -> s1.getAge() - s2.getAge())
    85. .limit(2)
    86. .collect(Collectors.toList());
    87. System.out.println(sortedCivilStudents);
    88. // skip
    89. // skip 操作与limit操作相反,如同其字面意思一样,是跳过前n个元素,比如我们希望找出排序在2之后的土木工程专业的学生,可以写为
    90. List<Student> civilStudents = students.stream()
    91. .filter(student -> "土木工程".equals(student.getMajor()))
    92. .skip(2)
    93. .collect(Collectors.toList());
    94. // 映射处理
    95. // 在sql中,借助select 关键字后面添加需要的字段名称,可以仅输出需要的字段数据,而流式处理的映射操作也是实现这一目的,在jdk8的流式处理中,主要包含两类映射操作,map,和flatMap
    96. // map
    97. // 例子
    98. /*
    99. 需求背景 : 假设希望筛选出所有专业为计算机科学的学生姓名,那么可以在filter筛选的基础上,通过map将学生实体映射为学生姓名字符串
    100. */
    101. List<String> collect3 = students.stream().filter(student -> "计算机科学".equals(student.getMajor())).map(Student::getName).collect(Collectors.toList());
    102. System.out.println(collect3);
    103. // 除了上面这类基础的map,java8还提供了mapToDouble(ToDoubleFunction<? super T> mapper),mapToInt(ToIntFunction<? super T> mapper),mapToLong(ToLongFunction<? super T> mapper),
    104. // 这些映射分别返回对应类型的流,java8为这些流设定了一些特殊的操作,比如我们希望计算所有专业为计算机科学学生的年龄之和,那么我们可以实现如下:
    105. long sum = students.stream().filter(student -> "计算机科学".equals(student.getMajor())).mapToInt(Student::getAge).sum();
    106. System.out.println(sum);
    107. // 上面是通过将student按照年龄直接映射为intStream ,可以直接调用提供的sum()方法来达到目的,此外使用这些树之流的好处在于可以避免jvm装箱操作带来的性能损耗
    108. // flatMap
    109. // flatMap与map的区别在于 flatMap是将一个流中的每个值都转换成一个个流,然后再将这些流扁平化称为一个流,距离说明,假设我们有一个字符串数组
    110. // String[] strs= {'java8',"is","easy","to","learn"} 希望输出构成这一组数组的所有非重复自负,可能首先会想到
    111. String[] strs = {"jdk8", "is", "easy", "to", "learn"};
    112. List<String[]> distinctStrs = Arrays.stream(strs)
    113. .map(str -> str.split("")) // 映射成为Stream<String[]>
    114. .distinct()
    115. .collect(Collectors.toList());
    116. System.out.println(distinctStrs);
    117. // 但是执行之后得到的结果是一个包含多个字符串, 构成一个字符串的字符数组 的流,此时执行distinct操作是给予在这些字符串数组之间的对比,所以达不到目的
    118. // dinstinct 还有对一个包含多个字符的客流进行操作的时候才能达到目的,即,对 Stream<String> 进行操作,此时flatMap就可以达到目的
    119. List<String> distinctStrs1 = Arrays.stream(strs)
    120. // 相当于吧 str对象映射成字符串流对象
    121. .map(str -> str.split("")) // 映射成为Stream<String[]>
    122. // 扁平化为 字符流对象
    123. .flatMap(Arrays::stream) // 扁平化为Stream<String>
    124. .distinct()
    125. .collect(Collectors.toList());
    126. /*
    127. flatMap将由map映射得到的Stream<String[]>,转换成由各个字符串数组映射成的流Stream<String>,再将这些小的流扁平化成为一个由所有字符串构成的大流Steam<String>,从而能够达到我们的目的。
    128. 与map类似,flatMap也提供了针对特定类型的映射操作:
    129. flatMapToDouble(Function<? super T,? extends DoubleStream> mapper),flatMapToInt(Function<? super T,? extends IntStream> mapper),flatMapToLong(Function<? super T,? extends LongStream> mapper)
    130. */
    131. // 前述为中间操作
    132. // 终端操作
    133. /*
    134. 是流式处理的最后一步,可以在终端操作中实现对流查找,规约等操作
    135. */
    136. // FIXME 查找
    137. // allMatch
    138. // allMatch用于检测是否全部都满足指定的参数行为,如果全部满足则返回true,例如我们希望检测是否所有的学生都已满18周岁,那么可以实现为:
    139. boolean b = students.stream().allMatch(student -> student.getAge() >= 18);
    140. // anyMatch
    141. // 用来检测是否存在一个或者多个满足制定的参数行为,如果满足则返回true,加入希望检测是否有来自武汉大学的学生,可写
    142. boolean 武汉大学 = students.stream().anyMatch(student -> student.getSchool().equals("武汉大学"));
    143. // noneMatch
    144. // 用于检测是否不存在满足制定行为的元素,如果不存在则返回true,
    145. boolean flag = students.stream().noneMatch(student -> student.getMajor().equals("计算机科学"));
    146. // findFirst 用于返回满足条件的第一个元素,findFirst 不携带参数,,具体查找条件可以使用filter进行设置,此外,findFirst返回的是一个optional类型
    147. // findAny 用于返回任意一个满足过滤条件的元素
    148. // 实际上对于顺序流式处理而言,findFirst和findAny返回的结果是一样的,至于为什么会这样设计,
    149. // 接下来我们介绍的并行流式处理,当我们启用并行流式处理的时候,查找第一个元素往往会有很多限制,如果不是特别需求,在并行流式处理中使用findAny的性能要比findFirst好。
    150. // reduce 归约
    151. // 前面例子中的方法 大多都是通过collect(Collectors.toList()) 对数据进行封装返回,如果我的目标不是返回一个新的集合,
    152. // 而是希望对经过参数化的操作后的集合进一步的运算,可用集合实施规约操作,jdk8 流式处理中提供了reduce方法来达到这一目的
    153. // 前面我们通过mapToInt将Stream<Student>映射成为IntStream,并通过IntStream的sum方法求得所有学生的年龄之和,实际上我们通过归约操作,也可以达到这一目的,实现如下:
    154. int totalAge = students.stream()
    155. .filter(student -> "计算机科学".equals(student.getMajor()))
    156. .mapToInt(Student::getAge).sum();
    157. // 归约操作
    158. int totalAge1 = students.stream()
    159. .filter(student -> "计算机科学".equals(student.getMajor()))
    160. .map(Student::getAge)
    161. .reduce(0, (a, b) -> a + b);
    162. // 进一步简化
    163. int totalAge2 = students.stream()
    164. .filter(student -> "计算机科学".equals(student.getMajor()))
    165. .map(Student::getAge)
    166. .reduce(0, Integer::sum);
    167. // 采用无初始值的重载版本,需要注意返回Optional
    168. Optional<Integer> totalAge3 = students.stream()
    169. .filter(student -> "计算机科学".equals(student.getMajor()))
    170. .map(Student::getAge)
    171. .reduce(Integer::sum); // 去掉初始值
    172. // 收集
    173. // 前面利用collect(Collectors.toList())是一个简单的收集操作,是对处理结果的封装,对应的还有toSet、toMap,以满足我们对于结果组织的需求。这些方法均来自于java.util.stream.Collectors,我们可以称之为收集器。
    174. //
    175. //收集器也提供了相应的归约操作,但是与reduce在内部实现上是有区别的,收集器更加适用于可变容器上的归约操作,这些收集器广义上均基于Collectors.reducing()实现。
    176. // todo 求学生的总人数
    177. // * Returns a {@code Collector} accepting elements of type {@code T} that
    178. // * counts the number of input elements. If no elements are present, the
    179. // * result is 0.
    180. Long collect4 = students.stream().collect(Collectors.counting());
    181. // 进一步简化
    182. long sum2 = students.stream().count();
    183. // 求年龄的最大值,最小值
    184. // 求最大年龄
    185. Optional<Student> olderStudent = students.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()));
    186. // 进一步简化 ,, 由原先的比对逻辑变为
    187. /** comparing
    188. * Accepts a function that extracts a {@link java.lang.Comparable
    189. * Comparable} sort key from a type {@code T}, and returns a {@code
    190. * Comparator<T>} that compares by that sort key.
    191. *
    192. maxby
    193. */
    194. // * Returns a {@code Collector} that produces the maximal element according
    195. // * to a given {@code Comparator}, described as an {@code Optional<T>}.
    196. // *
    197. //
    198. Optional<Student> olderStudent2 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));
    199. // 求最小年龄
    200. /*
    201. * Returns a {@code Collector} that produces the minimal element according
    202. * to a given {@code Comparator}, described as an {@code Optional<T>}.
    203. *
    204. */
    205. Optional<Student> olderStudent3 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)));
    206. // 年龄总和
    207. students.stream().collect(Collectors.summingInt(Student::getAge));
    208. // 字符串拼接
    209. students.stream().map(Student::getName).collect(Collectors.joining(","));
    210. // TODO 并行流式处理
    211. // 流式处理中的很多都适合采用 分而治之 的思想,从而在处理集合较大时,极大的提高代码的性能,java8的设计者也看到了这一点,所以提供了 并行流式处理。上面的例子中我们都是调用stream()方法来启动流式处理,java8还提供了parallelStream()来启动并行流式处理,parallelStream()本质上基于java7的Fork-Join框架实现,其默认的线程数为宿主机的内核数。
    212. //
    213. // 启动并行流式处理虽然简单,只需要将stream()替换成parallelStream()即可,但既然是并行,就会涉及到多线程安全问题,所以在启用之前要先确认并行是否值得(并行的效率不一定高于顺序执行),另外就是要保证线程安全。此两项无法保证,那么并行毫无意义,毕竟结果比速度更加重要。
    214. }
    215. }