Stream 流常见基本操作

简介: Stream 流常见基本操作

概述

Stream 流是 Java8 新特性之一,我们在实际开发中借助 Stream 流搭配 Lambda 表达式,可以很方便的完成一些对集合的操作,可以显著的提升开发的效率和性能。

一、Stream 流的常见生成方式

通常我们会选择对以下类型生成 Stream 流:单列集合、双列集合、数组、零散的数据。

public class StreamDemo {
    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体系的集合间接的生成流
        Map<String,Integer> map = new HashMap<String, Integer>();
        Stream<String> keyStream = map.keySet().stream();
        Stream<Integer> valueStream = map.values().stream();
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

        //数组可以通过Arrays中的静态方法stream生成流
        String[] strArray = {"hello","world","java"};
        Stream<String> strArrayStream = Arrays.stream(strArray);
      
        //同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
        Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
        Stream<Integer> intStream = Stream.of(10, 20, 30);
    }
}

二、Stream 流中间操作方法

1、常用中间操作方法

这里的中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作(其他中间操作或终结操作)

方法名 说明
Stream filter(Predicate predicate) 用于对流中的数据进行过滤
Stream limit(long maxSize) 获取前几个元素
Stream skip(long n) 跳过前几个元素
Stream distinct() 元素去重,依赖(hashCode和equals方法)
Stream sorted(Comparator<? super T> comparator) Stream 流元素排序
static Stream concat(Stream a, Stream b) 合并a和b两个流为一个流
Stream map(Function<T,R> mapper) 转换流中的数据类型

Stream.contact 合并两个 stream 流的时候,如果一个 stream 是 a 类型,另一个 stream 是 b 类型,那么最终合并为 a,b的父类型(类型提升)。

2、使用示例1

public class FilterDemo {
  // 获取以张开头的元素
    public static void main(String[] args) {
       // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<>();
        list.add("张三丰");
        list.add("张无忌");
        list.add("张翠山");
        list.add("王二麻子");
        list.add("张良");
        list.add("谢广坤");

        list.stream().filter(s ->s.startsWith("张")).forEach(s-> System.out.println(s));

    }
}

3、使用示例2

public class LimitAndSkipDemo {
    public static void main(String[] args) {
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        //需求1:取前3个数据在控制台输出
        list.stream().limit(3).forEach(s-> System.out.println(s));
        System.out.println("--------");

        //需求2:跳过3个元素,把剩下的元素在控制台输出
        list.stream().skip(3).forEach(s-> System.out.println(s));
        System.out.println("--------");

        //需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
        list.stream().skip(2).limit(2).forEach(s-> System.out.println(s));
    }
}

4、使用示例3

public class SortedDemo {
  // 按照年龄进行排序
    public static void main(String[] args) {
        ArrayList<String> manList = new ArrayList<>();
        Collections.addAll(manList,"蔡坤坤,24","叶厚贤,23,","刘不甜,22","吴倩,24","谷加,30","肖亮亮,27");

        manList.stream()
                .sorted((o1,o2)->Integer.parseInt(o1.split(",")[1]) - Integer.parseInt(o2.split(",")[1]))
                .forEach(x -> System.out.println(x));
    }
}

5、使用示例4

public class ConcatAndDistinctDemo {
    public static void main(String[] args) {
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        //需求1:取前4个数据组成一个流
        Stream<String> s1 = list.stream().limit(4);

        //需求2:跳过2个数据组成一个流
        Stream<String> s2 = list.stream().skip(2);

        //需求3:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
        Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s));
    }
}

6、使用示例5

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

        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"张无忌-15","周芷若-14","赵敏-13","张强-20","张三丰-100","张翠山-40");

        // 需求:只获取里面1的年龄并进行打印
        List<Integer> collect = list.stream()
                .map(s -> Integer.parseInt(s.split("-")[1]))
                .collect(Collectors.toList());
        System.out.println(collect);

    }
}

7、Stream 流使用注意事项

注意1:修改 Stream 流中的数据,不会影响原来集合或者数组中的数据。

注意2:中间方法,返回新的 Stream 流,原来的 Stream 流只能使用一次,建议使用链式编程()。因为每次调用中间方法都会返回新的 Stream 流,原来的 Stream 流会关闭。如:

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

        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        Stream<String> stream1 = list.stream().filter(s -> s.startsWith("张"));
        Stream<String> stream2 = stream1.filter(s -> s.length() == 3);
        stream2.forEach(s -> System.out.println(s));

        stream1.filter(s -> s.length() == 2);
    }
}

三、Stream 流终结操作方法

1、常用终结方法

终结操作的意思是,执行完终结方法之后,Stream 流将不能再执行其他操作了。

方法名 说明
void forEach(Consumer action) 遍历
long count() 统计个数
toArray() 收集流中的数据,放到数组中
R collect(Collector collector) 收集流中的数据,放到集合中

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

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

注意1toSet() 会进行自动去重。

注意2toMap() 如果我们要收集到 Map 集合当中,键重复会报错。

2、使用示例1

public class CountAndForEachDemo {
    public static void main(String[] args) {
    
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<>();
        list.add("张三丰");
        list.add("张无忌");
        list.add("张翠山");
        list.add("王二麻子");
        list.add("张良");
        list.add("谢广坤");


        // 统计返回此流中的元素数
        long count = list.stream().count();
        System.out.println(count);

        // 遍历打印流中元素
        list.stream().forEach(x -> System.out.println(x));

    }
}


3、使用示例2

public class ToArrayDemo {
    public static void main(String[] args) {
    
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<>();
        list.add("张三丰");
        list.add("张无忌");
        list.add("张翠山");
        list.add("王二麻子");
        list.add("张良");
        list.add("谢广坤");

        // 收集流中的数据,放到数组中 (Object 数组)
        Object[] objects = list.stream().toArray();
        System.out.println(Arrays.toString(objects));

        // 收集流中的数据,放到数组中 (指定类型 数组)
        // toArray 方法参数就是创建一个指定类型的数组
        // 底层会依次得到流里面的每一个数据,并把数据放到数组当中
        // 返回值就是一个装着流里面所有数据的数组
        String[] strings = list.stream().toArray(valuecount -> new String[valuecount]);
        System.out.println(Arrays.toString(strings));

    }
}

4、使用示例3

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

        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"张无忌-男-15","周芷若-女-14","赵敏-女-13","张三丰-男-100","谢广坤-男-41");

        // 需求1:把所有男性收集到list集合当中
        List<String> newList1 = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toList());
        System.out.println(newList1);

        // 需求2:把所有男性收集到set集合当中
        Set<String> newList2 = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toSet());
        System.out.println(newList2);

        // 需求3:把所有男性收集到map集合当中(键为姓名,值为年龄)
        Map<String, Integer> map = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toMap(
                        s -> s.split("-")[0],
                        s -> Integer.parseInt(s.split("-")[2])));
        System.out.println(map);
    }
}

5、Stream 基本分组功能

方法名 说明
Collector<T, ?, Map<K, List>> groupingBy(Function<? super T, ? extends K> classifier) 这是最基本的 groupingBy 方法,classifier:键映射:该方法的返回值是键值对的 键

Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)

这个重载方法在基本的 groupingBy 方法的基础上添加了一个 downstream 参数,downstream:值映射:通过聚合方法将同键下的结果聚合为指定类型,该方法返回的是键值对的 值

Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier mapFactory, Collector<? super T, A, D> downstream)

这个重载方法除了包含分类函数和 downstream 收集器外,还接收一个 mapFactory 参数,mapFactory:无参构造函数提供返回类型:提供一个容器初始化方法,用于创建新的 Map容器 (使用该容器存放值对)


public class GroupDemo {
  // 基本分组操作
    public static void main(String[] args) {
        // 初始化数据
        Actor actor1 = new Actor("张三",18);
        Actor actor2 = new Actor("李四",20);
        Actor actor3 = new Actor("王五",22);
        List<Actor> actors = new ArrayList<>();
        Collections.addAll(actors,actor1,actor2,actor3);
        // 按照姓名分组
        Map<String, List<Actor>> collect = actors.stream().collect(Collectors.groupingBy(actor -> actor.getName()));
        // 打印结果
        System.out.println(collect);

        // 将不同课程的学生进行分类
        Map<String, List<Actor>> groupByCourse = actors.stream().collect(Collectors.groupingBy(actor -> actor.getName()));
        System.out.println(groupByCourse
        );
        Map<String, List<Actor>> groupByCourse1 = actors.stream().collect(Collectors.groupingBy(actor -> actor.getName(), Collectors.toList()));
        System.out.println(groupByCourse1);

        // 上面的方法中容器类型和值类型都是默认指定的,容器类型为:HashMap,值类型为:ArrayList
        // 可以通过下面的方法自定义返回结果、值的类型
        Map<String, List<Actor>> groupByCourse2 = actors.stream()
                .collect(Collectors.groupingBy(actor -> actor.getName(),
                        () -> new HashMap<>(),
                        Collectors.toList()));
        System.out.println(groupByCourse2);
    }
}

其他分组操作参考:Stream Collectors.groupingBy 的四种用法

四、Stream 综合小练习

public class Test {
    public static void main(String[] args) {
        // 现有两个 ArrayList 集合:
        // 第一个集合:存储 6 名男演员的名字和年龄;第二个集合:存储 6 名女演员的名字和年龄
        ArrayList<String> manList = new ArrayList<>();
        ArrayList<String> womanList = new ArrayList<>();
        Collections.addAll(manList,"蔡坤坤,24","叶厚贤,23,","刘不甜,22","吴倩,24","谷加,30","肖亮亮,27");
        Collections.addAll(womanList,"赵晓英,35","杨颖,36","高媛媛,43","刘诗,35","杨小敏,33");

        // 需求1:男演员只要名字为 3 个字的前两人
        Stream<String> stream1 = manList.stream().filter(s -> s.split(",")[0].length() == 3)
                .limit(2);

        // 需求2:女演员只要姓杨的,并且不要第一个
        Stream<String> stream2 = womanList.stream().filter(s -> s.split(",")[0].startsWith("杨"))
                .skip(1);

        // 需求3:把过滤后的男演员姓名和女演员合并到一起封装成 Actor 对象(Actor 对象属性有 name、age)
        Stream<Actor> stream3 = Stream.concat(stream1, stream2)
                .map(s -> new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1])));

        // 需求5:将所有的演员对象都报存到 List 集合中
        stream3.collect(Collectors.toList()).forEach(s -> System.out.println(s));

    }
}

小结

Stream 流的使用步骤?

  1. 获取 Stream 流对象
  2. 使用中间方法处理数据
  3. 使用终结方法处理数据

如何获取 Stream 流对象?

  1. 单列集合:Collection 中的默认方法
  2. 双列集合:不能直接获取,可借助 keySets、values 转换成单列集合间接获取
  3. 数组:Arrays 工具类型中的静态方法
  4. 零散的数据:Stream 接口中的静态方法 of

Stream 常见方法?

  1. 中间方法:filter、limit、skip、distinct、concat、map
  2. 终结方法:forEach、count、collect

Lambda 怎么简化?

  1. 首先,数据类型可以省略
  2. 如果形参只有一个,小括号可以省略
  3. 如果形参有多个,小括号就不能省略了
  4. 如果方法体只有一行,那么大括号可以省略,return 可以省略分号可以省略

注:上述所有方法皆为函数式接口,因此均可以使用 Lambda 表达式进行替换,或使用方法引用替换(下期介绍)。


相关文章
|
1月前
|
Java API 数据处理
探索 Java 8 中的 Stream 流:构建流的多种方式
探索 Java 8 中的 Stream 流:构建流的多种方式
|
1月前
|
Java
【Java】Stream流是什么,如何使用Stream流?
【Java】Stream流是什么,如何使用Stream流?
51 0
|
1月前
|
存储 Java API
Stream流
Stream流
39 1
|
19天前
|
存储 Java API
Java——Stream流(1/2):Stream流入门、Stream流的创建(认识Stream、体验Stream流、Stream流的使用步骤、获取Stream流的方法)
Java——Stream流(1/2):Stream流入门、Stream流的创建(认识Stream、体验Stream流、Stream流的使用步骤、获取Stream流的方法)
25 0
|
20天前
|
API
Stream流知识
Stream流知识
5 0
|
1月前
|
前端开发 数据库
【Stream流】Sort排序详解
【Stream流】Sort排序详解
48 0
|
9月前
|
Java
stream流操作
stream流操作
51 0
|
1月前
|
Java
Stream流教程
Stream流教程
41 0
|
10月前
stream流排序
stream流排序
57 0
|
11月前
|
Java 数据处理
常用Stream流
常用Stream流
115 0