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静态方法ofStream.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};//查重后对元素进行*10Arrays.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 5Stream.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=1var 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.0double 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出现的次数12出现的次数23出现的次数1a出现的次数2b出现的次数1*/ps:我们依旧有更简便的方法 想了解就来小编主页看看吧