Stream流&Optional

5、强大的SteamAPI

  • Java8两个重要的改变,第一个是Lambda表达式,另一个则是StreamAPI
  • Stream API(java.util.stream)把真正的函数式编程风格引入到java中。这是目前为止对Java类库最好的补充,因为StreamAPI可以极大提供Java程序员的生产力,效率高
  • Stream是java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用StreamAPI对集合数据进行操作,就类似于使用SQL执行的数据库查找。也可以使用StreamAPI来执行操作,简言之,StreamAPI提供了一种高效且易于使用的处理数据的方式

5.1、为什么使用StreamAPI

关系型数据库是把过滤在sql语句中体现了,但是有时候需要在java层面来过滤数据(例如:非关系数据库,把所有的数据都查询出来,然后需要java层面来过滤)

Stream和Collection集合的区别

​ Collection是一种静态的内存数据结构,而stream是有关计算的。前者主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算

5.2、Stream简介

​ Stream是数据渠道,用户操作数据源(集合、数组等)所生成的元素序列,集合是数据的容器,Stream是操作数据的。

​ 下面有几点是在使用Stream流需要注意的。

  1. Stream本身不会存储元素
  2. Stream不会改变源对象。相反,它会返回一个持有结果的新Stream实例
  3. Stream操作时延迟执行的。这意味着Stream流的操作会等到程序需要结果的时候才执行

5.3、Stream的操作步骤

image-20230328214031725

  • 步骤一:创建Stream

    通过一个数据源(例如:数组,集合等)获取一个流对象

  • 步骤二:中间操作

    一个中间操作(就是一系列的对数据源的操作),对数据源的数据进行相关处理

  • 步骤三:终止操作(终端操作)

    一旦执行终止操作,才执行中间操作链(延时执行的体现),并产生结果。一旦执行了终止操作,Stream实例就不能使用了

5.3.1、创建Stream对象的四种方式
  • 方式一:通过集合

    java8中的Collection接口被扩展,提供了两个获取流对象(Stream)的方法

    • default Stream<E> stream();返回一个顺序流
    • default Stream<E> parallelStream();返回一个并行流

    并行流&串行流

    • 并行流:把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。与串行流相比,并行流可以很大程度上提高程序的执行效率,java8中将并行进行了优化,我们可以很容易的对数据进行并行处理,StreamAPI可以通过parallel()和sequential()两个方法在并行流与串行流之间进行切换
  • 方式二:通过数组

    java8中的Arrays的静态方法stream()可以获取数组流。而且还提供了基本数据类型的创建Stream的重载方法

    • static <T> Stream<T> Stream(T[] arrys);返回一个数组流
    • public static IntStream stream(int[] array);
    • public static LongStream stream(long[] array);
    • public static DoubleStream stream(double[] array);
  • 方式三:通过Stream的of()方法

    可以调用Stream类静态方法of(),通过显示值创建一个流。它可以接受任意数量的参数

    • public static <T> Stream<T> of(T…values);返回一个流
  • 方式四:创建无限流

    可以使用静态方法Stream.iteratr()和Stream.generate()两个方法创建无限流(无限流就是可以创建无限个元素对应的Stream实例对象)

    • public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f) //迭代

      说明:UnaryOperator<T>是一个函数式接口,是Function<T>函数是接口的子接口,

    • public static<T> Stream]<T> generate(Supplier<T> s) //生成

    //无限流可以帮助我们生成一些数据,下面的案例如果没有使用limit()中间操作和System.out::println终止操作
    
    //案例:遍历前10个偶数
    //下面用到了终止操作和中间操作 第一个参数是种子,这里表示从0开始
    //public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f)
    Stream.interate(0,t->t+2).limit(10).forEach(System.out::println);
    
    //public static<T> Stream<T> generate(Supplier<T> sup)
    Stream.generate(Math::random).limit(10).forEach(System.out::println);
    
5.3.2、Stream的中间操作

​ Stream流的中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而是在终止操作时一次性全部处理,这种行为可以称为“惰性求值”,下面介一些常用的中之操作

1.筛选与切片
方法描述
filter(Predicate p)接收Lambda,从流中排除某些元素
distinct()筛选,通过流所生成元素的hashCode()和equals()去除重复元素
limit(long maxSize)截断流,使其元素不超过给定数目
skip(long n)跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
2.映射
方法描述
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream
flatMap(Function f)接收一个函数作为参数,将流中的每个值多换成另一个流,然后把所有流连接成一个流

案例:

//flatMap(Function f)一个接受一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流(是用于将嵌套的Stream)
  
    //将字符串中的多个字符构成的集合转换为对应的Stream的示例
    public void fromStringStream(String str){
    ArrayList<Character> list=new ArrayList<>();
    for(Character c:str.toCharArray()){
        list.add(c);
    }
    return list.stream(); //什么意思呢?  就是将一个str流分成很多个Character流,然后将这些Character流合成一个流   
3.排序
方法描述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator com)产生一个新流,其中按比较器顺序排序
5.3.3、Stream的终止操作
  • 终止操作会从流的流水线生产结果。其结果可以是任何不是流的值,例如:List,Integer ,甚至是void
  • 流进行了终止操作后,不能再次使用
1.Stream匹配与查找
方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中的元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中的最小值
forEach(Consumer c)内部迭代(使用Collection接口需要用户去做迭代,称为外部迭代。相反,Stream API使用内部迭代——它帮你把迭代做完了)

案例:

//allMatch(Predicate p)  作用:检查是否匹配所有元素
//练习:是否所有的员工的年龄多大于18
boolean allMatch=employees.stream().allMatch(e->e.getAge()>18) 
  
//noneMatch(Predicate p) 作用:检查是否没有匹配的元素 
 //练习:是否存在员工姓“雷”
    employees.stream().noneMatch(e->e.getName().startsWith("雷"));

//findFirst() 作用:返回第一个元素
//注意这里的Optinal类是一个容器,会在下面讲到
Optional<Employee> employee=employees.stream().findFirst();

2.归约
方法描述
reduce(T iden,BinartOperator b)可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回Optional<T>

map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名

image-20230329214452340

@Slf4j
public class StreamReduceTest {
    //测试map_reduce模式
    @Test
    public void ReduceTest(){
        List list=new ArrayList();
        for (int i = 0; i < 10; i++) {
            list.add(ThreadLocalRandom.current().nextInt(1000));
        }
        //使用中间操作map返回的是串行流
        Stream stream = list.stream();
        log.info("正在完成中间操作map:");
        Stream streamString = stream.map(el1 -> {
            //将整型流变化为了字符串流
            return el1 + "map处理";
        });
        log.info("正在完成归约操作:");
        //使用reduce(归约)操作
        Optional reduce = streamString.reduce((el1, el2) -> {
            return el1 + "\n" + el2;
        });
        //得到Optional中的数据
        log.info("使用map_reduce得到的结果是:");
        reduce.ifPresent(System.out::println);

    }
}
3.收集
方法描述
collect(Collector c)将流转换为其他形式。接受一个Collector接口的实现,用于给Stream中元素做汇总的方法
  • Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到List、set、Map),Collector的类结果图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5brrOk4u-1680098645078)(null)]

  • 另外,Collectors实用类提供了很多静态方法,可以方便地创建常见收集器示例,具体方法与实现如下表:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XeCLpkql-1680098645293)(null)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQ3HWO4M-1680098644089)(null)]

6、Optional类

**总结:**Optional类就是一个为避免程序中出现空指针异常的工具类(包装类)。

​ 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java8类库的一部分。

​ Optional<T>类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在 Optional可以更好的表达这个概念。并且可以避免空指针异常,该类使用final修饰

​ Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

​ Optional提供的了很多的方法可以帮助我们进行控制检测。

  • 创建Optional类对象的方法

    • Optional.of(T t) :创建一个Optional实例,t必须非空
    • Optional.empty() :创建一个空的Optional实例
    • Optional.ofNullable(T t ): 创建一个Optional实例,t可以为null
  • 判断Optional容器中是否包含对象:

    • boolean isPresent() : 判断是否包含对象

    • void ifPresent(Consumer<? extends T> other):如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它,如果为null,不执行任何操作

    • public Optional filter(Predicate<? super T> predicate); 可以对optional包装的数据进行一些特殊的判断,根据自定条件返回true和false,但是这个返回的boolean值会像基本数类型的包装类一样把boolean转换为一个Optional的对象。(下面是关于filter的源码)

      public Optional<T> filter(Predicate<? super T> predicate) {
              Objects.requireNonNull(predicate);
              if (!isPresent())
                  return this;
              else
                  return predicate.test(value) ? this : empty();
          }
      //这里给出了Predicate的部分代码
      @FunctionalInterface
      public interface Predicate<T> {
      	 boolean test(T t);
      }
      
  • 获取Optional容器的对象

    • T get() :如果调用对象包含值,返回该值,否则抛异常

    • T orElse(T other) :如果有值则将其返回,否则返回指定的other对象

    • TorElseGet(Supplier<? extends T> other):如果有值则将其返回,否则返回由Supplier接口实现提供的对象。

    • TorElseThrow(Supplier<? extends X>exceptionSupplier):如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

      案例:

      //下面示例的Boy和Girl是一个POJO类
      //没有注意空指针的写法
      public String getGirlName(Boy boy){
          return boy.getGirl().getName();
      }
      
      @Test
      public void test03(){
          Boy boy=new Boy();
          //这里Boy存在一个传入一个Girl示例的构造方法 这种情况很容其出现空指针异常,例如boy并没没有设置Girl属性
          String girlName=getGirlName(boy);
          
      }
      
      //优化以后的getGirlName()方法
      //原始避免空指针的写法:
      public String getGirlName1(Boy boy){
          if(boy!=null){
              Girl girl=boy.getGirl();
              if(girl!=null){
                  return girl.getName();
              }
          }
          return null;
      }
      //使用Optinal类避免空指针的写法:
      public String getGirlName2(Boy boy){
          //这里不能用of()方法,因为传进来的参数本来可能就是null
          Optional<Boy> boyOptional=Optional.ofNullable(boy);
          //调用Optional 类的检查空指针的方法orElse(T other);
          Boy boy=boyOptional.orElse(new Boy(new Girl("FrairyHome")));
          //下面的操作一定可以避免空指针
      	Girl g=    boy.getGirl();
          //但是这里Girl实例对象可能为null,
      	Optional<Girl> GirlOptinal= Optional.ofNullable(g);
      	Girl girl01 = GirlOptional.orElse(new Girl("FairyHome"));
          return girl01.getName();
      }
      
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值