乐趣区

JDK8-强大的Stream

JDK8 中强大的 Stream

2013 年发布的 JDK8 可以说是深入人心
今天这篇文章主要讨论一下 JDK8 中的新特性Stream

Stream 的介绍

  • Stream 被译为‘流’但他并非是 io 流那种传输流 而是是对集合 (Collection) 对象功能的增强 包括一些对集合和数组处理的 ‘S’ 操作
  • 另外 Stream 包含了并行操作 把控得当的话会大大提高程序效率
  • 个人感觉 JDK 从第八代 (或者再早一点) 就慢慢的开始研究并实现像 GoogleGuava、Hutool 等早些年就很有风采的库里面的一些代码
    当然了我们 java 跟 C 的关系相必也不用多说了吧 哈哈哈(开个玩笑)

Stream 的基本使用

在这里我只解释比较常用的操作 尽量做到比较好的有效的讨论
(先抱好西瓜再起捡芝麻吧)

  • Stream 的创建

    • Stream.of(…);(静态方法 创建有限长度)
    • Collection.stream() (集合内的创建方式)返回串行流
    • Collection.parallelStream() 返回并行流

      • 并行操作可以根据开发需求进行使用 效果比较明显
    • Arrays.stream(数组引用)
// 通过 Stream 静态方法 of
Stream.of(1,2,3,"a","b","c");
// 通过集合
var list=new ArrayList();
list.stream();
// 通过数组(只能接受 int[] double[] long[])
int[] int={1,2,3,4,5};
Arrays.stream(int);
  • Stream 的操作步骤

    • Intermediate(中级操作)
      可以执行多次的操作
    • Terminal(终极操作)
      只能执行一次操作 必须有的操作
    • Short-circuiting(短路操作)
      只能执行一次

Stream 的中端操作(Intermediate)

可以执行多次 对 Stream 中的元素过滤 排序等
虽然中端操作可以执行多层处理 但是并不是一个中端操作就遍历一次
可以认为转换操作是 lazy(惰性求值)的,只有在 Terminal 操作执行时,才会一次性执行

  • filter 对 Stream 指定条件过滤
    filter 传入的 Lambda 表达式必须是 Predicate 实例,
    参数可以为任意类型,而其返回值必须是 boolean 类型。
class Stu{
    private String name;
    private int age;
    private double money;
    ...
}
var list=List.of(new Stu("Asen",18,100.0),
            new Stu("Bsen",28,2000.0),
            new Stu("Csen",38,3000.0)
        );
        // 要求找出集合中存储 Stu 的年龄为 18 的元素并打印
list.stream().filter(s->s.getAge()==18).forEach(out::println);
        // 一行代码解决是不是要比普通遍历爽的多
  • distinct 对 Stream 中元素进行查重
int[] i={1,2,3,4,5,2,3,4};
// 对数组进行查除重复的元素
Arrays.stream(i).distinct().forEach(out::println);
//1,5
  • map 有 mapToDouble,mapToInt,mapToLong 三种变形
    即是对 Stream 中数据进行处理或转换类型
int[] i={1,2,3,4,5,2,3,4};
// 查重后对元素进行 *10
Arrays.stream(i).distinct()
      .map(i->i*10).forEach(out::println);
//10,50
// 将数组中数据转换为 double 类型
Arrays.stream(i).distinct()
      .mapToDouble(i->i).forEach(out::println);
//1.0,5.0
  • flatMap 传入的 Lambda 表达式必须是 Function 实例
    参数可以为任意类型,而其返回值类型必须是一个 Stream
Stream.of(1,1,1,1)
      .flatMap(v->Stream.of(v*10)).forEach(out::println);
//10 10 10 10
  • concat 对两个 Stream 进行拼接
    若有一个 Stream 是并行的,则新的 Stream 也是并行的
Stream.concat(Stream.of(1,2,3),Stream.of(4,5))
      .forEach(out::println);
// 打印结果
// 1  2  3  4  5  
  • skip 去掉元素中前几个元素
  • peek 创建一个包含 Stream 中所有元素的 新 Stream
    并且还可以提供一个新的消费函数 此消费函数会先执行
Stream.of(1, 2, 3, 4, 5)
      .skip(3)
      .peek(integer -> out.println("accept:" + integer))
      .forEach(out::println);
//  accept:4
//  4
//  accept:5
//  5
  • sorted 对元素进行排序
    一个重载方法 有参需要传入指定规则 如:Integer::compareTo
Stream.of(1, 3, 4, 2, 5)
      .sorted()
      .forEach(out::println);
      //1 2 3 4 5
Stream.of(1, 3, 4, 2, 5)
      .sorted(Comparator.reverseOrder())
      .forEach(out::println);
      //5 4 3 2 1

Stream 的终端操作(Terminal)

中端操作只能执行一次 将值返回 或者输出内容
并且 Stream 不能被二次利用如:二次打印 二次转换

  • forEachOrdered forEach 前面一直在用的终端操作否则看不到结果
    前者按照元素插入顺序进行遍历
    后者如果 Stream 指定了顺序则按照指定顺序
  • count 返回元素个数
vat int=Stream.of(1,2,3,4).count();
//int = 4
  • max min 根据指定的 Comparator 进行查找最大或最小值
    返回一个 Optional,该 Optional 中的 value 值就是 Stream 中最小的元素
    两个完全根据规则而定 所以 max 也可以找出最小值 min 也可以找出最大值
var list=List.of(1,2,3,2,4);
var max=list.stream().max((o1,o2)->o2-o1);
// 在 max 中设置规则找出最小值 o=1
var min=list.stream().min((o1,o2)->o1-o2);
// 在 min 中设置找出最大值
  • 对数据的简单处理:
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3,4).reduce(0,Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3,4).reduce(Integer::sum).get();
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0,-3.0,-2.0)
.reduce(Double.MAX_VALUE, Double::min);
  • collect 是一个终端操作 同时也可以认为是一个高端操作
    比如对 Stream 中的元素生成集合 栗子如下:
  • 生成 List toList
var tolist=Stream.of(2,1,3,2,4,3)
                 .distinct()
                 .sorted()
                 .collect(Collectors.toList());
out.println(tolist);//    [1, 2, 3, 4]
默认为 ArrayList
  • 生成 set 头 toSet
var toset=Stream.of("a","b","c","2")
                .distinct()
                .sorted()
                .collect(Collectors.toSet());
out.println(toset);//[a, 2, b, c]
默认为 HashSet
  • 如果想指定生成集合类型就要用到 toCollection
var collectors=Stream.of(1,2,3,4,5)
    .collect(Collectors.toCollection(ArrayList::new));
  • 生成 map
    对 map 集合的生成可想而知是比较复杂的 而且在转换时会出现一些问题 不能像正常 map 一样使用 但是我们可以解决
Student[] stu= {new Student("Asen", 02, 10000),
                new Student("Byin", 01, 2000),
                new Student("Dfei", 03, 2000),
                new Student("Cjie", 03, 300)};
// 把学生的 ID 作为键 name 作为值 
// 此时按照普通 map 的创建的话 ID=03 的新值会将旧值覆盖
// 但是按照 toMap 则要进行处理
var stumap=Arrays.stream(stu)
                 .sorted(comparing(Student::id))
                 .collect(Collectors.toMap(Student::id, Student::name));
// 这种情况肯定是报错的 下面具体说明解决办法
  • toMap 的大坑之一:如果有相同的值则会抛出异常
    解决办法:
Student[] stu= {new Student("Asen", 02, 10000),
                new Student("Byin", 01, 2000),
                new Student("Dfei", 03, 2000),
                new Student("Cjie", 03, 300)};
// 这时候要传入第三个参数 代表新值覆盖旧值
var stumap=Arrays.stream(stu)
                 .sorted(comparing(Student::id))
                 .collect(Collectors.toMap(Student::id, Student::name,(x,l)->l));
// 但是大家有没有想过 如果值为 null 呢 当然也是不能正常使用的下面具体说明
  • toMap 的大坑之二:如果值为 null 也会抛出异常
    解决办法:
var nullmap=Arrays.stream(stu)
.sorted(comparing(Student::id))
.collect(Collectors
            .toMap(Student::id, s-> s.name()==null?"":s.name(),(l,x)->x));
// 简单的对值进行判断 如果为 null 则替换为指定字符
  • Collectors.joining 将 Stream 转换为字符串
// 如果为纯字符串类型
Stream.of("1","2","3","4").collect(Collectors.joining(","));
// 如果为数值类型
list.stream().map(r -> r.toString()).collect(Collectors.joining(","));
ps: 如果想要更简便的集合与字符串的相互转换 可以继续跟进小编哦

Stream 的短路操作 Short-circuiting

  • allMatch 判断断 Stream 中的元素是否 全部 满足指定条件 如果全部满足条件返回 true 否则返回 false
boolean allMatch=Stream.of(1,2,3,4,5)
                       .allMatch(i->i>0);
// allMatch=true
  • anyMatch 判断元素中是否最少有一个满足条件 有一个就返回 true 全部不满足则返回 false
boolean anyMatch=Stream.of(1,2,3,4,5)
                       .allMatch(i->i<0);
// anyMatch=false
  • noneMatch 判断 Stream 中元素是否全部不满足指定条件 全不满足返回 true 反之返回 false
boolean noneMatch=Stream.of(1,2,3,4,5)
                       .noneMatch(i->i<0);
// noneMatch=true
  • limit 从 Stream 中截取指定个数的元素 如果 Stream 元素个数小于指定个数 则返回全部
Stream.of(1,2,3,4,5)
      .limit(3).forEach(out::println);
// 1,2,3

Stream 扩招操作

关于 Stream 的常用操作就讲到这里了
下面说一些稍微高级一点的 ’sao’ 操作

  • 请看题 1:要求对一个字符串挑出其中的英文字母的个数
    普通方式我们不做具体说明
// 普通方法应该就是遍历然后判断了吧 但是我们可以利用并行操作进行高效查找 并且这样的链式结构他看着不爽吗 哈哈哈
String st="12asbs*346";
var vi=toList(convert(char[].class,st)).parallelStream()
        .flatMap(vv->Stream.of((char)vv))
        .filter(ss->ss>='A'&&ss<='Z'||ss>='a'&&ss<='z').count();
// vi=4
// 当然这种方法显然也是比较冗余的 但是多一种思路总不算坏吧
//ps 如果想了解更简便的方法 请继续跟进小编博客
  • 请看题 2:要求对 Stream 中元素按照奇数偶数分组
    当然了 大家在实际应用中可以结合数组 字符串操作

    • 补充 partitioningBy 返回 map 集合 数据分组
      只能按照 Boolean 进行分组 也就是说 partitioningBy 传入的 lambda 表达式只能是返回 Boolean 类型
var map=Stream.of(1,2,3,4,5)
              .collect(Collectors.partitioningBy(it->it%2==0));
//{false=[1, 3, 5], true=[2, 4]}
var map3=Stream.of(1,2,3,4,5)
                .collect(Collectors.partitioningBy(it->it%2==0,Collectors.counting()));
//{false=3, true=2}
  • 请看题 3:要求对 Stream 中元素统计每个元素出现的个数 娟姐的小徒弟们你们想到了啥 嗯 -_- 哈哈哈哈

    • 补充 groupingBy 返回 map 也属于数据分组
      但是按照任意类型进行分组
var map=Stream.of("1","2","a","a","3","b","2")
                .sorted()
                .collect(Collectors.groupingBy(ic->ic,Collectors.counting()));
map.forEach((k,v)->out.println(k+"出现的次数"+v));
/* 1 出现的次数 1
2 出现的次数 2
3 出现的次数 1
a 出现的次数 2
b 出现的次数 1 */
ps:我们依旧有更简便的方法 想了解就来小编主页看看吧
退出移动版