Java 集合通过 Stream 流过滤提取

在 .NET 的 C# 语言中,对集合过滤提取的技术是 Linq,其链式编程风格简便易读,深受大家喜爱。那么 Java 是否也提供了类似的技术?答案肯定的,那就是 Java 使用 Stream 流对集合元素进行过滤提取,只不过其功能没有 C# 中的 Linq 那么丰富。但不管怎么说,这已经算是很不错了,毕竟省去了频繁使用循环遍历过滤提取集合元素的繁琐步骤。

下面我们就针对每种集合类型,演示一下 Stream 流过滤集合元素数据的技术吧。


一、生成 Stream 流

对于 List 和 Set 这种 Collection 类型的集合,只需要调用其 Stream() 方法,即可生成 Stream 流。
List 接口的常用实现类为 ArrayList 和 LinkedList 。Set 接口的常用实现类为 HashSet 和 TreeSet 。

对于 Map 类型的集合,需要先将 Map 转换为 Collection 类型的集合,间接生成 Stream 流。
常用的 Map 类有 HashMap 和 TreeMap。

对于数组来说,需要通过 Arrays 类中的静态方法 Stream 来生成 Stream 流。

也可以通过 Stream 的静态方法 of 来将一些相同类型的临时数据组成一个集合,生成 Stream 流。

具体代码如下所示:

import java.util.*;
import java.util.stream.Stream;

//不同集合生成 Stream 流
public class StreamTest {
    public static void main(String[] args) {
        //Collection 类型的集合可以使用 stream() 生成流
        List<String> list = new ArrayList<String>();
        Stream<String> listStream = list.stream();

        Set<String> set = new HashSet<String>();
        Stream<String> setStream = set.stream();

        //Map 类型的集合可以先转换为 Collection 类型的集合,间接生成流
        Map<String, Integer> map = new HashMap<String, Integer>();
        //对于 Map 的键集合,先转换为 Set 集合,然后生成流
        Stream<String> keyStream = map.keySet().stream();
        //对于 Map 的值集合,先转换为 Collection 集合,然后生成流
        Stream<Integer> valueStream = map.values().stream();
        //对于 Map 的键值对集合,先转换为 Set 集合,然后生成流
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

        //数组可以通过 Arrays 中的静态方法 stream 生成流
        String[] strArray = {"jobs", "alpha", "monkey", "wolfer"};
        Stream<String> strArrayStream = Arrays.stream(strArray);

        //可以通过 Stream 接口的静态方法 of(T... values) 将相同类型的一组数据生成流
        Stream<String> strArrayStream2 = Stream.of("winston", "reid", "celio");
        Stream<Integer> intStream = Stream.of(11, 22, 33, 44);
    }
}

二、Stream 流的常用方法

Stream 流的常用方法如下:

方法名 说明
Stream filter(Predicate predicate) 对流中的数据进行过滤筛选
Stream limit(long maxSize) 获取流中前几个元素
Stream skip(long n) 跳过流中前几个元素
static Stream concat(Stream a, Stream b) 将 a 流和 b 流合并成一个流
Stream distinct() 获取流中的不同的元素(根据 Object.equals(Object) 判断元素是否相同)
void forEach(Consumer action) 对此流的每个元素进行遍历
long count() 获取流中的元素个数

我们最经常使用的是 filter 方法,其代码演示如下:
import java.util.ArrayList;

public class StreamTest {
    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<String>();
        list.add("任肥肥");
        list.add("候胖胖");
        list.add("任我行");
        list.add("蔺赞赞");
        list.add("任政富");
        list.add("乔豆豆");

        //要求把【以任开头的字符串】打印出来
        list.stream().filter(s->s.startsWith("任")).forEach(System.out::println);

        System.out.println("---------");

        //要求获取【以任开头的字符串】的数量
        long count = list.stream().filter(s -> s.startsWith("任")).count();
        System.out.println(count);
    }
}

/*
打印的结果如下所示:
任肥肥
任我行
任政富
---------
3
*/

limit 方法和 skip 方法的代码演示如下:
import java.util.ArrayList;

public class StreamTest {
    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<String>();
        list.add("任肥肥");
        list.add("候胖胖");
        list.add("任我行");
        list.add("蔺赞赞");
        list.add("任政富");
        list.add("乔豆豆");

        //只打印前 3 个
        list.stream().limit(3).forEach(System.out::println);
        System.out.println("---------");

        //跳过前 3 个,打印剩余的
        list.stream().skip(3).forEach(System.out::println);
        System.out.println("---------");

        //跳过前 2 个,再打印剩余元素的前 2 个
        list.stream().skip(2).limit(2).forEach(System.out::println);
    }
}

/*
打印的结果如下所示:
任肥肥
候胖胖
任我行
---------
蔺赞赞
任政富
乔豆豆
---------
任我行
蔺赞赞
*/

concat 方法和 distinct 方法的代码演示如下:
import java.util.ArrayList;
import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<String>();
        list.add("任肥肥");
        list.add("候胖胖");
        list.add("任我行");
        list.add("蔺赞赞");
        list.add("任政富");
        list.add("乔豆豆");

        //提取前 3 个元素,生成一个流
        Stream<String> s1 = list.stream().limit(3);

        //跳过前 1 个元素,使用后面 3 个元素生成一个流
        Stream<String> s2 = list.stream().skip(1).limit(3);

        //将两个流合并,然后打印出合并后的流的元素
        Stream<String> s3 = Stream.concat(s1, s2);
        s3.forEach(System.out::println);

        System.out.println("---------");

        /*
        上面的 s1, s2, s3 这三个流,
        一旦遇到 forEach 或 count 方法,执行完后,流就全部释放了,不能再用。
        因此下面再进行流的过滤时,即使是相同的元素的流,也需要重新生成流
        */

        //将两个流合并,去重后,进行打印
        Stream.concat(list.stream().limit(3), list.stream().skip(1).limit(3))
                .distinct().forEach(System.out::println);
    }
}

/*
打印的结果如下所示:
任肥肥
候胖胖
任我行
候胖胖
任我行
蔺赞赞
---------
任肥肥
候胖胖
任我行
蔺赞赞
*/

三、Stream 流中数据的收集

上面的代码示例,只是通过 Stream 流把集合数据进行过滤,一旦在最后执行了 forEach 或 count 等方法后,Stream 流就会释放销毁,后续就无法使用了。此时我们肯定想把 Stream 流中的数据提取出来,放到集合中。这就需要用到以下相关的方法:

方法名 说明
R collect(Collector collector) 把结果收集到集合中

Collectors 工具类,提供了具体的收集方式:

方法名 说明
public static Collector toList() 把元素收集到List集合中
public static Collector toSet() 把元素收集到Set集合中
public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map集合中

具体代码示例如下所示:
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {

        ArrayList<String> personlist = new ArrayList<String>();
        personlist.add("任肥肥,38岁");
        personlist.add("候胖胖,40岁");
        personlist.add("任我行,55岁");
        personlist.add("蔺赞赞,34岁");
        personlist.add("任政富,58岁");
        personlist.add("乔豆豆,33岁");

        //把【以任开头的字符串】提取出来,存储到新的 List 集合中,并打印出来
        Stream<String> listStream1 = personlist.stream().filter(s -> s.startsWith("任"));
        List<String> list = listStream1.collect(Collectors.toList());
        for (String p : list) {
            System.out.println(p);
        }

        System.out.println("---------");

        //把【以任开头的字符串】提取出来,提取出【姓名】,存储在 Set 集合中,并打印出来
        Stream<String> listStream2 = personlist.stream()
                .filter(s -> s.startsWith("任")).map(s->s.split(",")[0]);
        Set<String> set = listStream2.collect(Collectors.toSet());
        for(String name : set) {
            System.out.println(name);
        }

        System.out.println("---------");

        //把【以任开头的字符串】提取出来,提取出【姓名】和【年龄】,存放到 Map 集合中
        //在 Map 集合中,姓名为键,年龄为值。然后打印出来
        Stream<String> listStream3 = personlist.stream().filter(s -> s.startsWith("任"));
        Map<String, String> map =
                listStream3.collect(
                        Collectors.toMap(s -> s.split(",")[0], s -> s.split(",")[1]));
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            System.out.println(key + " <---> " + map.get(key));
        }
    }
}

/*
打印的结果如下所示:
任肥肥,38岁
任我行,55岁
任政富,58岁
---------
任我行
任肥肥
任政富
---------
任我行 <----> 55岁
任肥肥 <----> 38岁
任政富 <----> 58岁
*/

当然有关 Stream 流操作集合元素的方法还有很多,比如 max() 方法,min() 方法等等,这里就不介绍了,大家在工作中需要用到的时候,查询以下 JDK 文档即可,总之使用起来都很简单。希望本篇博客能够对大家有所帮助。



posted @ 2022-01-13 17:37  乔京飞  阅读(12626)  评论(0编辑  收藏  举报