JDK8 stream 流看这一篇就够了

一、stream简介

        Stream API是Java 8中加入的一套新的API,主要用于处理集合操作,不过它的处理方式与传统的方式不同, 称为“数据流处理”。流(Stream)类似于关系数据库的查询操作,是一种声明式操作。

        根据我个人的理解stream就是将数据转换成一个流,然后通过封装的API 对数据进行操作,stream的API基本上能实现各种数据操作。如果将集合中的数据看做是数据库中的表,那么stream的API则相当于SQL语言的各种语句。能够对数据进行筛选和各种条件查询。

二、函数式接口

        因为stream中的API基本上都是基于函数式编程来写的。所以要想使用好stream,那就必须先掌握函数式编程,理解函数式接口。

        函数式接口定义: 只包含一个抽象方法的接口,称为函数式接口

        如何判断一个接口是不是函数接口呢?

  • 通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。

     

  • 在任意函数式接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

        

   是不是很简单,接下来我们可以自己定义一个函数式接口来玩一下。

   

package com.xingli.springlearningdemo.stream;

/**
 * description: FunctionInterfaceDemo <br>
 *
 * @date: 2021/7/3 0003 下午 4:09 <br>
 * @author: William <br>
 * version: 1.0 <br>
 */
public class FunctionInterfaceDemo {
    public static void main(String[] args) {
        Dog dog = () -> System.out.println("这是一个小阔爱");
        dog.say();
    }

}

interface Dog{
    public void say();
}

 执行后输出如下

三、Java内置四大核心函数式接口

关于函数式接口阳哥的PPT曾经做过总结,我直接拿来给大家看一下:

下面逐一说一下这四个接口的用法。以为streamAPI中很多接口都是基于下面四种函数接口去写的。所以要想用好stream一定要很好的理解这四种接口。

下面每个接口我会一步一步的从匿名内部类-->拉姆达表达式 --> 简化版拉姆达表达式去逐一演示,便于理解为什么拉姆达表达式最终可以很简单。

Function<T, R>接口是一个函数型接口,接收一个参数,返回一个参数。对应日常操作包含入参和出参的方法。具体使用形式如下。

package com.xingli.springlearningdemo.stream;

import java.util.function.Function;

/**
 * description: StreamFunctionDemo <br>
 *
 * @date: 2021/7/3 0003 上午 10:24 <br>
 * @author: William <br>
 * version: 1.0 <br>
 */
public class StreamFunctionDemo {

    public static void main(String[] args) {

        // Function(T,R)   功能型接口   入参T 出参R
        // 匿名内部类写法
        Function<Integer,Integer> function = new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer t) {
                return t*2;
            }
        };
        System.out.println(function.apply(1));

        // 拉姆达表达式
        Function<Integer,Integer> function2 = t ->{return t*2;};
        System.out.println(function2.apply(1));

        // 简化版拉姆达表达式
        Function<Integer,Integer> function3 = t ->t*2;
        System.out.println(function3.apply(1));

    }
}

         接下来看一下Stream的API,比如常用的map和flatMap方法就使用了Function<T, R>接口。

现在在去调用这个方法是不是就熟悉了。就拿map方法来说它使用Function<T, R>接口那就是传入一个参数,输出一个参数。

举个例子来说:假设现在有一个list里面有一堆字符串,我们要得到字符串的长度,就能使用map方法。

List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
List<Integer> integerList = list.stream()
                                .map(s -> s.length())
                                .collect(Collectors.toList());


//简化版
List<Integer> integerList = list.stream()
                .map(String::length)
                .collect(Collectors.toList());

在这个例子中我们传入字符串,然后返回字符串的长度。剩下的三个接口可以自己在stream方法中找一下对应的方法,然后去调用熟悉一下,我就不再举例了。

Predicate<T> 是一个判定型接口,接收一个参数,返回固定Boolean值。具体用法如下:

package com.xingli.springlearningdemo.stream;

import java.util.function.Predicate;

/**
 * description: StreamPredicateDemo <br>
 *
 * @date: 2021/7/3 0003 下午 12:13 <br>
 * @author: William <br>
 * version: 1.0 <br>
 */
public class StreamPredicateDemo {

    // Predicate<T>    判定型接口   入参T 必须返回Boolean值
    public static void main(String[] args) {

        Predicate<Integer> predicate = new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer%2 == 0;
            }
        };
        System.out.println(predicate.test(5));

        Predicate<Integer> predicate1 = t ->{
            return t%2 == 0;
        };
        System.out.println(predicate1.test(5));

        Predicate<Integer> predicate2 = t -> t%2 == 0;
        System.out.println(predicate2.test(5));
    }
}

Supplier<T>是一个供给型函数,不接收参数,但是会返回一个参数。具体用法如下:

package com.xingli.springlearningdemo.stream;

import java.util.function.Supplier;

/**
 * description: StreamSupplierDemo <br>
 *
 * @date: 2021/7/3 0003 上午 10:27 <br>
 * @author: William <br>
 * version: 1.0 <br>
 */
public class StreamSupplierDemo {

    // Supplier<R>     供给型接口   没有入参 只有出参R
    public static void main(String[] args) {

        Supplier<String> supplier = new Supplier<String>() {
            @Override
            public String get() {
                return "123456";
            }
        };
        System.out.println(supplier.get());


        Supplier<String> supplier1 = ()->{
            return "123456";
        };
        System.out.println(supplier1.get());


        Supplier<String> supplier2 = ()-> "123456";
        System.out.println(supplier2.get());
    }
}

Consumer<T>是一个消费型接口,接收一个参数,但是没有任何返回值。具体用法如下:

package com.xingli.springlearningdemo.stream;

import org.elasticsearch.common.recycler.Recycler;

import java.util.function.Consumer;

/**
 * description: StreamConsumerDemo <br>
 *
 * @date: 2021/7/3 0003 下午 12:37 <br>
 * @author: William <br>
 * version: 1.0 <br>
 */
public class StreamConsumerDemo {

    // Consumer<T>     消费型接口   只有入参没有出参
    public static void main(String[] args) {

        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        consumer.accept("123456");

        Consumer<String> consumer2 = s -> {
            System.out.println(s);
        };
        consumer.accept("123456");

        Consumer<String> consumer3 = s -> System.out.println(s);
        consumer.accept("123456");

        Consumer<String> consumer4 = System.out::println;
        consumer.accept("123456");
    }

}

四、stream流式计算

        讲完了四大函数我们就能开始正式来说一下stream的一些方法了。

Stream流的获取方式

①通过集合Collection获取

List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc"));
Stream<String> stream = list.stream();

② 通过数组获取

Stream<String> stream2 = Arrays.stream(new String[]{"aaa", "bbb", "ccc"});

③ 直接获取

Stream<String> stream3 = Stream.of("aaa","bbb","ccc");

stream常用方法

1.filter用来对数据筛选

可以看到使用的是四大函数中的Predicate,这个函数接收一个参数,但是只返回固定的boolean值。

List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc"));
List<String> stringList = list.stream()
                              .filter(s -> s.length() == 3)
                              .collect(Collectors.toList());

2.limit 这个跟MySQL 中的limit基本上一样,就是取前多少条的意思

List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc"));
List<String> stringList = list.stream()
                .filter(s -> s.length() == 3)
                .limit(2)
                .collect(Collectors.toList());

3.distinct去重 这个方法也类似MySQL中的distinct函数

List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc","aaa"));
List<String> stringList = list.stream()
                .filter(s -> s.length() == 3)
                .limit(2)
                .distinct()
                .collect(Collectors.toList());

 4.skip跳过方法 

 需要注意的是这里是跳过前N个元素,像下面这个例子,第一步是筛选出来字符串长度为三的字符串,第二步是获取结果中的前五条,第三步是跳过结果中的前两条

List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc","aaa","ddd","eee"));
List<String> stringList = list.stream()
                .filter(s -> s.length() == 3)
                .limit(5)
                .skip(2)
                .distinct()
                .collect(Collectors.toList());

5.映射 map


对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。 
如:将 list 中每一个 Integer类型元素自增后转化为 String类型

//将集合中的每一个元素+1,并转为字符串
List<Integer> list = Arrays.asList(1,2,3,4,5);
List<String> result = list.stream()
                      .map(s->String.valueOf(++s))
                      .collect(Collectors.toList());
//统计集合中>3的元素数量
int result = list.stream().filter(s -> s > 3).count();


6. 合并多个流 flatMap

之前工作中用到过,写了一篇博客。可以参考jdk8 stream流合并list​​​​​​​

​​​​​​​​​​​​​​
7.匹配元素

①是否匹配任一元素:anyMatch

anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。 

//判断流中是否含有>10的项
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
boolean result = list.stream()
                     .anyMatch(x->x>10);

② 是否匹配所有元素:allMatch

allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。 

//判断流中是否全部>5
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
boolean result = list.stream()
                     .allMatch(x->x>5);

③ 是否未匹配所有元素:noneMatch

noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:

//判断流中是否 全部不满足 >5
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
boolean result = list.stream()
                     .noneMatch(x->x>5);

8.sorted排序

默认采用升序排列。

List<Integer> list = new ArrayList<>(Arrays.asList(1,3,5,4,2,7));
List<Integer> integerList = list.stream()
                                //默认排序采用升序排列
                                .sorted()
                                .collect(Collectors.toList());
System.out.println("integerList.toString() = " + integerList.toString());
List<Integer> integerList1 = list.stream()
        //默认排序采用升序排列
        .sorted((t1, t2) -> t2-t1)
        .collect(Collectors.toList());
System.out.println("integerList1.toString() = " + integerList1.toString());

输出如下:


9. 归约统计

归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出;
 使用基于数据流的方式,将流装载相应的 SummaryStatistics 来进行归约计算,可以实现更多的操作;

List<Integer> list = new ArrayList<>(Arrays.asList(1,3,5,4,2,7));
IntSummaryStatistics statistics = list.stream().mapToInt(x -> x).summaryStatistics();
//获取平均值
double average = statistics.getAverage();
//获取总个数
long count = statistics.getCount();
//获取最小值
int min = statistics.getMin();
//获取最大值
int max = statistics.getMax();
//获取总和
long sum = statistics.getSum();

10.parallelStream 并行处理(建议不用,可以自己定义线程池去操作,避免OOM异常)

parallelStream是流 进行并行处理的替代方案,parallelStream 的底层实现为 ForkJoin 线程池,JDK8 为为 parallelStream 提供了一个通用的线程池,对于代码实际运行时的 parallelStream 线程数量是不可控的,但是可以通过设置 JVM 运行参数 -Djava.util.concurrent.ForkJoinPool.common.parallelism=N (N为线程数量)来调整 JVM ForkJoinPool 的线程数量;

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//get count of empty string
int count = strings.parallelStream().filter(string -> string.isEmpty()).count();
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值