Java开发技巧——流式编程StreamAPI

1.什么是StreamAPI

和上次文章中介绍的Lamda表达式一样,StreamAPI是Java8中添加的一个新特性,可以使我们以声明的方式非常快速的操作集合,省去非常多的无用代码,现在让我们来举个例子!

目前有一个字符串数组,记录了小组内所有人的英文名,我们需要先将字母“B”开头的名字筛选出来,全部转换为小写,清切去重复后输出!

我们先使用for循环方式实现:

public class StreamAPI {
    
    
    public static void main(String[] args) {
    
    
        List<String> names = new ArrayList<>();
        names.add("Aaron");
        names.add("Abel");
        names.add("Bruce");
        names.add("Bruce");
        names.add("Brent");
        names.add("Caleb");
        List<String> newStr = new ArrayList<>();
        for(String name : names) {
    
    
            name = name.toLowerCase();
            if(name.startsWith("b") && !newStr.contains(name))
            {
    
    
               newStr.add(name);
               System.out.println(name);
            }
        }
    }
}

再使用StreamAPI方式实现:

public class StreamAPI {
    
    
    public static void main(String[] args) {
    
    
        List<String> names = new ArrayList<>();
        names.add("Aaron");
        names.add("Abel");
        names.add("Bruce");
        names.add("Bruce");
        names.add("Brent");
        names.add("Caleb");
        names.stream()
                .map(String::toLowerCase)
                .filter(n -> n.startsWith("b"))
                .distinct()
                .forEach(System.out::println);
    }
}

输出结果相同:
在这里插入图片描述

可以看到,这两种方式均可以完成相同功能,但是StreamAPI可以极大地减少无用代码,且不会被一些逻辑绕晕,可以说是学了之后可以提高开发效率!

2.StreamAPI的详细介绍

还是基于刚才的源码:

List<String> names = new ArrayList<>();
    names.add("Aaron");
    names.add("Abel");
    names.add("Bruce");
    names.add("Bruce");
    names.add("Brent");
    names.add("Caleb");

forEach遍历:

  • 描述:遍历Stream中的所有元素
  • 注意:此操作会结束链式编程(返回值为空)
names.stream()
      	.forEach(System.out::println);

或:

names.stream()
      	.forEach(n -> System.out.println(n));

既可以使用Lamda表达式形式,其中参数为流中的每个元素.也可以使用函数引用形式调用静态方法!

filter过滤:

扫描二维码关注公众号,回复: 11715647 查看本文章
  • 描述:对Stream中的每一项元素进行判断,留下符合条件的元素,排除不符合条件的元素,即可以使用Ladma表达式,也可以使用函数引用方式

filter过滤实现了此接口

 Stream<T> filter(Predicate<? super T> predicate);

用法示例:

 names.stream()
        .filter((n) -> n.startsWith("B"))//保留以B开头的字符串
        .forEach(n -> System.out.println(n));

同时此处可以显示实现接口,这样可以保留并多次使用,并且可以调用更高级的功能!

比如:

  • predicate.negate() 将不符合条件的元素留下,符合条件的元素删除
  • predicate.and(predicate2)判断是否同时符合多种条件,符合全部则保留
  • predicate.or(predicate2)判断是否同时符合多种条件,符合其一则保留
    Predicate<String> p1 = n -> n.startsWith("B");
        Predicate<String> p2 = n -> n.length()==5;
        names.stream()
                .filter(p1.negate().and(p2))//同时符合“不以B开头”,“长度为5”两个条件的字符串才保留
                .forEach(n -> System.out.println(n));

map,flatMap数据转换

  • 描述:将Stream中的元素进行逐一处理或转换,转换后既可以是原类型,也可以是不同类型。
names.stream()
            .map(n -> n.length())//取出每一个字符串的长度
            .forEach(n -> System.out.println(n));//此时输出的不再是字符串,而是长度

在这里插入图片描述
除了转换为其他类型,也可以转换为原类型

  names.stream()
            .map(n -> n.toLowerCase())//将每一个元素转换为小写
            .forEach(n -> System.out.println(n));//此时输出的依然是字符串

此时我们,在进行一个操作,将这些人的名字字母后逐个输出:

 names.stream()
                .map(n -> n.split(""))
                .forEach(n -> System.out.println(n));

结果:
在这里插入图片描述
我们现在想的到的是所有人名字的逐个字母,但是目前却将每个元素都转换为了字符数组,其实可以通过嵌套StreamAPI的方式来输出,但是,有没有一种方法一次性输出呢?

  • flatMap描述:将StreamAPI中的每个元素(Stream中包含Stream)

所以上文中,我们可以直接将数组再次转换为Stream,并且合并到原Stream中,这样可以就做到更多操作!

 names.stream()
            .flatMap(n -> Arrays.stream(n.split("")))
            .forEach(n -> System.out.println(n));

在这里插入图片描述

Limit/Skip截取,Distinct去重读,Sorted排序

  • 特点:上述操作统一称为:有状态操作,即操作过程需要和其它数据进行比较才可以得出结果,而之前所演示的命令均为无状态,也就是在执行的过程中仅考虑当前元素的操作即可!
names.stream()
        .flatMap(n -> Arrays.stream(n.split("")))
        .distinct() //将上次测试结果进行去重复
        .sorted()   //将去重复后的结果排序
        .skip(0)    //从第0个元素开始截取(跳过前0个元素)
        .limit(5)	//截取5个元素
        .forEach(n -> System.out.println(n));

结果:
在这里插入图片描述

parallel并行操作

我们之前所有的操作都是StreamAPI的串行操作,操作时StreamAPI会对其中每一个元素都进行顺序逐一处理,如果数据过多,会处理缓慢并阻塞线程,那么有没有办法一键进行多线程处理呢?

我们在之前案例的基础上让程序输出执行当前操作的线程:

names.stream()
        .flatMap(n ->
        {
    
    
            System.out.println(Thread.currentThread().getName());
            return Arrays.stream(n.split(""));
        })
        .forEach(n -> System.out.println(n));

在这里插入图片描述
可以看到目前是主线程按顺序进行数据处理!

  • parallel描述:可以让StreamAPI自动启动多个线程处理数据
names.stream()
        .parallel()//启动多线程处理数据
        .flatMap(n ->
        {
    
    
            System.out.println(Thread.currentThread().getName());
            return Arrays.stream(n.split(""));
        })
        .forEach(n -> System.out.println(n));

在这里插入图片描述
可以看到,StreamAPI自定启动了多条线程来帮助我们处理数据,但是也导致一个问题,由于每个线程处理速度不一样,所以输出结果也从原来的顺序输出变为混乱输出,所以Stream的parallel方法启动多线程处理有好处同样也有坏处,好处是可以帮我们节省很多无用代码比如线程创建的代码,坏处是也可能造成数据混乱等多线程中会出现的问题,请大家注意!

注意:有状态操作绝对不可以使用多线程操作!
多线程状态下会因为数据操作不统一的问题造成数据混乱,而有状态操作则需要和其他元素进行比较才可以得出结果,所以不能再多线程状态下使用有状态命令,极有可能出错!

   names.stream()
                .parallel()
                .flatMap(n ->
                {
    
    
                    System.out.println(Thread.currentThread().getName());
                    return Arrays.stream(n.split(""));
                })
                .sorted()
                .forEach(n -> System.out.println(n));

结果发现排序指令并没有生效~
在这里插入图片描述

总结:StreamAPI可以极大地省去无用代码,加快我们的开发进度,提升我们的心情,并且我上方举的例子都是基础操作,而StreamAPI功能非常强大,可以对很多集合进行操作,被操作的集合也不仅仅只有字符串集合,所以,这是一个非常强大的开发技巧,希望大家掌握!

猜你喜欢

转载自blog.csdn.net/qq_42628989/article/details/105332500
今日推荐