JAVA8流式编程

JAVA8流式编程

Scroll Down

一:什么是流式编程

对于java来说,我们最常用的面向对象编程属于命令式编程(Imperative Programming)这种编程范式。常见的编程范式还有逻辑式编程(Logic Programming),函数式编程(Functional Programming)。函数式编程java8也导入了,结合 Lambda 表达式,对于函数式接口的实现和使用变得灵活和简洁了。关于函数式接口以及Lambda表达式,今天不做详细的讲解和学习,今天的重点就是流式编程。流式编程是一个受到 函数式编程 和 多核时代影响而产生的东西。其实,流式编程就是基于JDK8 的Stream对于集合一系列的操作的流程定义。

二:为什么加入Stream

  1. 我们之前对于集合的操作,无论是找一个特定的对象,还是对集合类 特定的对象进行处理,或者排序,
  2. 更加麻烦的是,我们还会需要对集合进行处理后,返回一些符合要求的特定的集合,或者,多次操作,这些,JDK都没有提供任何的方法,我们都需要对结合进行遍历,写一段很冗余的代码。
  3. 所以JDK8加入了 java.util.stream包,实现了集合的流式操作,流式操作包括集合的过滤,排序,映射等功能。
  4. 根据流的操作性,又可以分为 串行流 和 并行流。
  5. 根据操作返回的结果不同,
  6. 流式操作又分为中间操作(返回一特定类型的结果)和最终操作(返回流本身)。大大方便了我们对于集合的操作。

三:什么是流

  1. JDK起名字还是很形象的,为什么叫流呢?
  2. 他不是一个数据结构,只是一个高级的迭代或者遍历,他就像是个管道,去处理水流一样,只能处理一次,
  3. 但是,处理完之后,可以把处理的水装起来,继续处理,或者直接拿走处理后你所需要的。
  4. 它内部对集合的处理采用了fork/join模式,JDK1.7加入的,针对并发处理的框架,这个也广泛应用于多线程和算法中,有兴趣的可以了解一下。
  5. 多个中间操作可以连接起来形成一个流水线,除非流水线终止操作,否则中间操作不会执行任何处理。终止操作时一次性全部处理,称为“延迟加载”

创建Stream流的四种方式

  • 通过Collection得Stream()方法(串行流)或者 parallelStream()方法(并行流)创建Stream
List<String> list = Arrays.asList("1","2","3","4","0","222","33");
Stream<String> stream = list.stream();
Stream<String> stream1 = list.parallelStream();
  • 通过Arrays中得静态方法stream()获取数组流
IntStream stream = Arrays.stream(new int[]{1,2,3});
  • 通过Stream类中得 of()静态方法获取流
Stream<String> stream = Stream.of("a","b","c");
  • 创建无限流(迭代、生成)
//迭代(需要传入一个种子,也就是起始值,然后传入一个一元操作)
Stream<Integer> stream1 = Stream.iterate(2, (x) -> x * 2);

//生成(无限产生对象)
Stream<Double> stream2 = Stream.generate(() -> Math.random());

四:常用的流式处理过程

streamsteep

  1. 在这个图中显示了过滤,映射,跳过,计数等多个环节
  2. 将这些步骤都放在一起进行一个流水线一样的操作,
  3. 整个过程在一个管道中完成,将数据又由原始状态转变为需要的状态。
  4. filter、map、skip都是在对函数模型进行操作,集合元素并没有真正被处理。
  5. 只有当终结方法count执行的时候,整个数据才会按照指定要求执行操作。
  6. 下文介绍各种使用场景,及个人的一些笔记记录

stream API的接口是函数式的,尽管java 8也引入了lambda表达式,但java实质上依然是由接口-匿名内部类来实现函数传参的,所以需要事先定义一系列的函数式接口。

五:常用的流操作

举个例子

image.png

流主要针对集合相关的操作,所有继承自Collection的接口都可以使用流,
default Stream stream() { return StreamSupport.stream(spliterator(), false); }
而stream也是一个接口,最后都是在ReferencePipeline这个类中实现的,我们先截取一下所有的方法:

stream

方法还是很多的,按照我们之前说的,根据操作返回结果不同,我们大致进行一下分类,也就是返回stream的就是中间操作,其他的,返回具体对象的就是最终操作:

中间操作:

filter(): 对元素进行过滤
sorted():对元素排序
map():元素映射
distinct():去除重复的元素

终止操作:

forEach():遍历每个元素。
findFirst():找第一个符合要求的元素。
reduce():把Stream 元素组合起来。例如,字符串拼接,数值的 sum,min,max ,average 都是特殊的 reduce。
collect():返回一个新的数据结构,基于Collectors有丰富的处理方法。
min():找到最小值。
max():找到最大值。

需要注意的是,一般中间操作之后,都是为了进行最终操作,得到我们需要的对象。

1.filter过滤操作

这个方法我们应该是我们挺常用的,也是很重要的一个方法。

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

这个是filter这个接口的定义,filter方法接收一个Predicate类型参数用于对目标集合进行过滤,我们再来看下它的实现:

    @Override
    public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
        Objects.requireNonNull(predicate);
        return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                     StreamOpFlag.NOT_SIZED) {
            @Override
            Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
                return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                    @Override
                    public void begin(long size) {
                        downstream.begin(-1);
                    }

                    @Override
                    public void accept(P_OUT u) {
                        if (predicate.test(u))
                            downstream.accept(u);
                    }
                };
            }
        };
    }

filter() 方法接收的是一个 Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数,因此,我们可以直接将一个 Lambda 表达式传递给该方法,比如说 element -> element.contains("王") 就是筛选出带有“王”的字符串。

  1. 举栗:得到set中不为null的集合
Set<Long> serviceSet = serviceSet.stream().filter((e) -> e != null).collect(Collectors.toSet());

2.map元素映射操作

接口:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

实现:
    @Override
    @SuppressWarnings("unchecked")
    public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
        Objects.requireNonNull(mapper);
        return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                     StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
            @Override
            Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
                return new Sink.ChainedReference<P_OUT, R>(sink) {
                    @Override
                    public void accept(P_OUT u) {
                        downstream.accept(mapper.apply(u));
                    }
                };
            }
        };
    }

1.举个栗子:得到某实体list中某个字段的集合

Set<Long> serviceSet = vos.stream().map(somethingVo::getServiceId).collect(Collectors.toSet());

List<Long> userIds = vos.stream().map(vo -> vo.getUserId()).collect(Collectors.toList()).stream().distinct().collect(Collectors.toList());

3. distinct():去除重复的元素

接口:
    Stream<T> distinct();

实现:
    @Override
    public final Stream<P_OUT> distinct() {
        return DistinctOps.makeRef(this);
    }

1.举个栗子:得到某实体list中某个字段去重后的集合

List<Long> distinctIds = vos.stream().map(somethingVo::getCorpId).distinct().collect(Collectors.toList());

4. forEach(): 逐一处理

typeList.forEach(task -> {
            eventMap.put(task.name(), task.getDescribe());
        });

5. groupingBy(),对list分组

Map<Long, List<DemoVo>> collect = list.stream().collect(Collectors.groupingBy(DemoVo::getId));
//多级分组
Map<Status, Map<String, List<Person>>> collect = persons.stream().collect(Collectors.groupingBy(Person::getStatusEnum,Collectors.groupingBy(Person::getSex)));

6. 对多个字段进行分组的另外一种形式

Function<KpiStatisticcVo, List<Object>> selfVoKey = vo -> Arrays.asList(vo.getPeriod(), vo.getServiceId());
Map<List<Object>, List<KpiStatisticcVo>> dailyNumMap = vos.stream().collect(Collectors.groupingBy(selfVoKey));

7. list去重

//方法1
List<String> collect = list.stream().distinct().collect(Collectors.toList());

//方法2
ArrayList<ServiceMemberVo> listCollect = list.stream()
                .collect(Collectors.collectingAndThen(Collectors
                        .toCollection(() -> new TreeSet<>(Comparator
                                .comparing(ServiceMemberVo::getId))), ArrayList::new));

8. reduce()操作

从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数

BigDecimal revenue = gmvVos.stream()
                    .map(i -> {
                        if (i.getRevenue() == null) {
                            return BigDecimal.ZERO;
                        } else {
                            return i.getRevenue();
                        }
                    }).reduce(BigDecimal::add).get().setScale(2, BigDecimal.ROUND_HALF_UP);

9.sorted()排序操作

sorted有两种方法,一种是不传任何参数,叫自然排序,还有一种需要传Comparator 接口参数,叫做定制排序。

 //自然排序
List<String> collect = list.stream().sorted().collect(Collectors.toList());
        
List<String> collect2 = list.stream().sorted((o1, o2) -> {
            if(o1.length()>o2.length()){
                return 1;
            }else 
            if(o1.length()<o2.length()){
                return -1;
            }else {
                return 0;

            }
        }).collect(Collectors.toList());

10. allMatch 检查是否匹配所有元素

boolean allMatch = persons.stream().allMatch((x) -> {
            return x.getStatusEnum().equals(Status.FREE);
        });

11. anyMatch 检查是否至少匹配一个元素

boolean allMatch = persons.stream().anyMatch((x) -> {
            return x.getStatusEnum().equals(Status.FREE);
        });

12.noneMatch 检查是否没有匹配所有元素

 boolean noneMatch= persons.stream().noneMatch((x) -> {
            return x.getStatusEnum().equals(Status.FREE);
        });

13.findFirst 返回第一个元素


14.max 返回流中最大值

Optional<Person> person = persons.stream().max((x,y) ->  Integer.compare(x.age, y.age));

15.min 返回流中最小值

Optional<Person> person = persons.stream().min ((x,y) ->  Integer.compare(x.age, y.age));

16.Collectors 将流转换成其它数据结构

//将流转为list
List<Person> collect = persons.stream().collect(Collectors.toList());
//将流转为hashSet
Set<String> collect = persons.stream().map(Person::getName).collect(Collectors.toSet());
//将流转为LinkedHashSet
Set<Integer> collect = persons.stream().map(Person::getAge).collect(Collectors.toCollection(LinkedHashSet::new));

六:参考文章

  1. 并发编程网
  2. J.U.C之executors框架:Fork/Join框架(1)原理
  3. java8系列-流式编程Stream