Java:Stream流

发布时间:2024年01月07日

以下代码使用JDK11编写。

1、体验Stream流

(1)案例需求

按照下面的要求完成集合的创建和遍历

  • 创建一个集合,存储多个字符串元素
  • 把集合中所有以"张"开头的元素存储到一个新的集合
  • 把"张"开头的集合中的长度为3的元素存储到一个新的集合
  • 遍历上一步得到的集合

(2)原始方式示例代码

public class StreamDemo01 {
    public static void main(String[] args) {
        //集合的批量添加
        ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));
        //list.add()

        //遍历list1把以张开头的元素添加到list2中。
        ArrayList<String> list2 = new ArrayList<>();
        for (String s : list1) {
            if(s.startsWith("张")){
                list2.add(s);
            }
        }
        //遍历list2集合,把其中长度为3的元素,再添加到list3中。
        ArrayList<String> list3 = new ArrayList<>();
        for (String s : list2) {
            if(s.length() == 3){
                list3.add(s);
            }
        }
        for (String s : list3) {
            System.out.println(s);
        }      
    }
}

(3)使用Stream流示例代码

public class StreamDemo01 {
    public static void main(String[] args) {
        //集合的批量添加
        ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));

        //Stream流
        list1.stream().filter(s->s.startsWith("张"))
                .filter(s->s.length() == 3)
                .forEach(s-> System.out.println(s));
    }
}

(4)Stream流的好处

  • 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印
  • Stream流把真正的函数式编程风格引入到Java中
  • 代码简洁
  • 结合了Lambda表达式,简化集合、数组的操作

2、Stream流的常见生成方式

(1)Stream流的思想

image-20231225142316558

(2)Stream流的三类方法

  • 获取Stream流
    • 创建一条流水线,并把数据放到流水线上准备进行操作
  • 中间方法
    • 流水线上的操作
    • 一次操作完毕之后,还可以继续进行其他操作
  • 终结方法
    • 一个Stream流只能有一个终结方法
    • 是流水线上的最后一个操作

(3)生成Stream流的方式

  • Collection体系集合:使用默认方法stream()生成流, default Stream stream()

  • Map体系集合:把Map转成Set集合,间接的生成流

  • 数组:通过Arrays中的静态方法stream生成流

  • 同种数据类型的多个数据:通过Stream接口的静态方法of(T… values)生成流

(4)代码演示

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);
    }
}

(5)详细操作

单列集合(Collection体系集合)

public class StreamDemo02 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"a","b","c","d","e");

        System.out.println("---------------一条语句:生成流并进行相关操作------------");
        list.stream()
                .forEach(s -> System.out.println(s));

        System.out.println("---------------把上面的一条语句进行拆分------------------");
        // 获取到一条流水线,并把集合中的数据放到流水线上
        Stream<String> stream1 = list.stream();
        // 使用终结方法打印一下流水线上的所有数据
        stream1.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                // s:依次表示流水线上的每一个数据
                System.out.println(s);
            }
        });
    }
}

image-20231225145314784

双列集合(Map体系的集合间接的生成流)

public class StreamDemo03 {
    public static void main(String[] args) {
        // 1.创建双列集合
        HashMap<String,Integer> hm = new HashMap<>();
        // 2.添加数据
        hm.put("aaa",111);
        hm.put("bbb",222);
        hm.put("ccc",333);
        hm.put("ddd",444);
        // 3.第一种获取stream流,只获取到了key
        hm.keySet().stream().forEach(s -> System.out.println(s));
        // 4.第二种获取stream流,可以获取到key和value
        hm.entrySet().stream().forEach(s-> System.out.println(s));
    }
}

image-20231225145356745

数组(数组可以通过Arrays中的静态方法stream生成流)

public class StreamDemo04 {
    public static void main(String[] args) {
        // 1.创建数组
        int[] arr1 = {1,2,3,4,5,6,7,8,9,10};
        String[] arr2 = {"a","b","c"};

        // 2.获取stream流
        Arrays.stream(arr1).forEach(s-> System.out.println(s));
        System.out.println("--------------------------------");
        Arrays.stream(arr2).forEach(s-> System.out.println(s));
        System.out.println("--------------------------------");


        /**
         * 注意:
         * Stream接口中静态方法of的细节:
         * 方法的形参是一个可变参数,可以传递一堆零散的数据,也可以传递数组
         * 但是数组必须是引用数据类型的,如果传递基本数据类型,是会把整个数组当做一个元素,放到Stream当中。
         */
        Stream.of(arr1).forEach(s-> System.out.println(s)); // 输出[I@17f6480

    }
}

image-20231225145955421

一堆零散数据(同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流)

public class StreamDemo05 {
    public static void main(String[] args) {
        // 基本类型
        Stream.of(1,2,3,4,5).forEach(s-> System.out.println(s));
        // 引用类型
        Stream.of("a","b","c","d","e").forEach(s-> System.out.println(s));
    }
}

image-20231225150148393

3、Stream流中间操作方法

注意1:如果是直接操作Stream流(如Stream.concat(stream1, stream2),而不是使用集合的stream格式,如list.stream()),中间方法返回的是新的Stream流,原来的Stream流只能使用一次。例如,用filter、limit、skip、concat、distinct、map等中间操作后,stream对象就无法再次被操作。建议使用链式编程或者把中间操作的结果存储到新的stream变量中再使用。

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

(1)概念:中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作

(2)常见方法

方法名说明
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) )组成的流
Stream map(Function<T,R> mapper)转换流中的数据类型

(3)filter代码演示

public class StreamDemo06 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");

        // 1、把张开头且字符串长度为3的留下,其余数据过滤不要
        System.out.println("--------filter过滤:匿名内部类格式---------");
        list.stream()
                .filter(new Predicate<String>() {
                    @Override
                    public boolean test(String s) {
                        //如果返回值为true,表示当前数据留下
                        //如果返回值为false,表示当前数据舍弃不要
                        return s.startsWith("张")&&s.length()==3;

                    }
                })
                .forEach(s -> System.out.println(s));

        System.out.println("--------filter过滤:Lambda表达式格式---------");
        list.stream()
                .filter(s -> s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(s -> System.out.println(s));

        System.out.println("--------filter过滤:Lambda表达式格式---------");
    }
}

image-20231225172117019

(4)limit&skip代码演示

public class StreamDemo06 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");
        
	    System.out.println("----limit获取前几个元素----");
        list.stream().limit(3).forEach(s -> System.out.println(s));
        System.out.println("----skip跳过前几个元素----");
        list.stream().skip(4) .forEach(s -> System.out.println(s));
 
        
        System.out.println("----练习:思路1----");
        /**
         * 课堂练习:获取 "张强", "张三丰", "张翠山"
         *
         * 第一种思路:先获取前面6个元素:"张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山",
         * 然后跳过前面3个元素
         */
        list.stream().limit(6).skip(3).forEach(s -> System.out.println(s));

        /**
         * 第二种思路:
         * 先跳过3个元素:"张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤"
         * 然后再获取前面3个元素:"张强", "张三丰", "张翠山"
         */
        System.out.println("----练习:思路2----");
        list.stream().skip(3).limit(3).forEach(s -> System.out.println(s));
    }
}

image-20231225172535981

image-20231225172943810

(5)concat&distinct代码演示

public class StreamDemo07 {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        Collections.addAll(list1, "张无忌","张无忌","张无忌", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");

        ArrayList<String> list2 = new ArrayList<>();
        Collections.addAll(list2, "周芷若", "赵敏");


        System.out.println("----------------distinct元素去重---------");
        list1.stream().distinct().forEach(s -> System.out.println(s));
        System.out.println("----------------concat合并a和b两个流为一个流---------");
        Stream.concat(list1.stream(),list2.stream()).forEach(s -> System.out.println(s));
    }
}

在这里插入图片描述

(6)map代码演示

public class StreamDemo08 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌-15", "周芷若-14", "赵敏-13", "张强-20", "张三丰-100", "张翠山-40", "张良-35", "王二麻子-37", "谢广坤-41");

        //需求:获取里面的年龄并进行打印
        System.out.println("--------map转换流中的数据类型:匿名内部类格式---------");
        /**
         * 第一个类型:流中原本的数据类型
         * 第二个类型:要转成之后的类型
         * apply的形参s:依次表示流里面的每一个数据,其返回值表示转换之后的数据
         * 当map方法执行完毕之后,流上的数据就变成了整数
         * 所以在下面forEach当中,s依次表示流里面的每一个数据,这个数据现在就是整数了
         */
        list.stream().map(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                String[] arr = s.split("-");
                String ageString = arr[1];
                int age = Integer.parseInt(ageString);
                return age;
            }
        }).forEach(s-> System.out.println(s));

        System.out.println("--------map转换流中的数据类型:Lambda表达式格式---------");
        list.stream()
                .map(s ->Integer.parseInt(s.split("-")[1]))
                .forEach(s-> System.out.println(s));
    }
}

在这里插入图片描述

4、Stream流终结操作方法

(1)概念:终结操作的意思是执行完此方法之后,Stream流将不能再执行其他操作

(2)常见方法

方法名说明
void forEach(Consumer action)对此流的每个元素执行操作
long count()返回此流中的元素数
toArray()收集流中的数据,放到数组中
R collect(Collector collector)收集流中的数据,放到集合中

(3)代码演示

forEach

public class StreamDemo09 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");


        /**
         * void forEach(Consumer action)           遍历
         * Consumer的泛型:表示流中数据的类型
         * accept方法的形参s:依次表示流里面的每一个数据
         * 方法体:对每一个数据的处理操作(打印)
         */
        System.out.println("---------forEach:匿名内部类方式-----------");
        list.stream().forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
        System.out.println("---------forEach:Lambda表达式方式-----------");
        list.stream().forEach(s -> System.out.println(s));
    }
}

在这里插入图片描述

count

public class StreamDemo09 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");
        
        // long count()统计
        System.out.println("---------count()统计-----------");
        long count = list.stream().count();
        System.out.println(count);

    }
}

image-20231225210610419

5、Stream流的收集操作

(1)概念:对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中

(2)常用方法

方法名说明
toArray()收集流中的数据,放到数组中
R collect(Collector collector)把结果收集到集合中

(3)工具类Collectors提供了具体的收集方式

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

(4)收集到数组中

public class StreamDemo10 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");

        // 需求:收集流中的数据,放到数组中-toArray()

        System.out.println("---------toArray():匿名内部类方式-----------");
        /**
         * IntFunction的泛型:具体类型的数组
         * apply的形参:流中数据的个数,要跟数组的长度保持一致
         * apply的返回值:具体类型的数组
         * 方法体:就是创建数组
         *
         * toArray方法的参数的作用:负责创建一个指定类型的数组
         * toArray方法的底层,会依次得到流里面的每一个数据,并把数据放到数组当中
         * toArray方法的返回值:是一个装着流里面所有数据的数组
         */
        String[] arr = list.stream().toArray(new IntFunction<String[]>() {
            @Override
            public String[] apply(int value) {
                return new String[value];
            }
        });
        System.out.println(Arrays.toString(arr));

        System.out.println("---------toArray():Lambda表达式方式-----------");
        String[] arr2 = list.stream().toArray(value -> new String[value]);
        System.out.println(Arrays.toString(arr2));
    }
}

image-20231225211812415

(5)收集到集合中

注:如果要收集到Map集合当中,键不能重复,否则会报错

package com.ya.mystream;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Stream流的收集操作:collect(Collector collector) 收集流中的数据,放到集合中 (List Set Map)
 */
public class StreamDemo11 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20",
                "张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");

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

        System.out.println("--------收集Set集合当中-----------");
        //把所有的男性收集起来,放到Set集合当中
        Set<String> newList2 = list.stream().filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toSet());
        System.out.println(newList2);


        System.out.println("--------收集Map集合当中:匿名内部类方式-----------");
        /**
         * 把所有的男性收集起来,放到Map集合当中
         * 键:姓名。 值:年龄
         */
        Map<String, Integer> map = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                /*
                 *   toMap : 参数一表示键的生成规则
                 *           参数二表示值的生成规则
                 *
                 * 参数一:
                 *       Function泛型一:表示流中每一个数据的类型
                 *               泛型二:表示Map集合中键的数据类型
                 *
                 *        方法apply形参:依次表示流里面的每一个数据
                 *               方法体:生成键的代码
                 *               返回值:已经生成的键
                 *
                 *
                 * 参数二:
                 *        Function泛型一:表示流中每一个数据的类型
                 *                泛型二:表示Map集合中值的数据类型
                 *
                 *       方法apply形参:依次表示流里面的每一个数据
                 *               方法体:生成值的代码
                 *               返回值:已经生成的值
                 *
                 * */
                .collect(Collectors.toMap(
                        // key
                        new Function<String, String>() {
                            @Override
                            public String apply(String s) {
                                //张无忌-男-15
                                return s.split("-")[0];
                            }
                        },
                        // value
                        new Function<String, Integer>() {
                            @Override
                            public Integer apply(String s) {
                                return Integer.parseInt(s.split("-")[2]);
                            }
                        }));
        System.out.println(map);

        System.out.println("--------收集Map集合当中:Lambda表达式方式-----------");
        Map<String, Integer> map2 = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toMap(
                        // key
                        s -> s.split("-")[0],
                        // value
                        s -> Integer.parseInt(s.split("-")[2])));

        System.out.println(map2);
    }
}

在这里插入图片描述

6、Stream流综合练习

6.1 练习1

  • 案例需求
    • 定义一个集合,并添加一些整数 1,2,3,4,5,6,7,8,9,10
    • 过滤奇数,只留下偶数
    • 并将结果保存起来
public class Test1 {
    public static void main(String[] args) {

        //1. 定义一个集合
        ArrayList<Integer> list = new ArrayList<>();
        //2.添加一些整数
        Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        //3.过滤奇数,只留下偶数
        //进行判断,如果是偶数,返回true 保留
        List<Integer> newList = list.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());
        //4.打印集合
        System.out.println(newList);
    }
}

image-20231225220507255

6.2 练习2

  • 案例需求
    • 创建一个ArrayList集合,并添加以下字符串,字符串中前面是姓名,后面是年龄
      “zhangsan,23”
      “lisi,24”
      “wangwu,25”
    • 保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值。
public class Test2 {
    public static void main(String[] args) {

        //1.创建一个ArrayList集合
        ArrayList<String> list = new ArrayList<>();
        //2.添加以下字符串
        list.add("zhangsan,23");
        list.add("lisi,24");
        list.add("wangwu,25");
        //3.保留年龄大于等于24岁的人
        System.out.println("--------收集为Map集合:匿名内部类方式--------");
        Map<String, Integer> map = list.stream()
                .filter(s -> Integer.parseInt(s.split(",")[1]) >= 24)
                .collect(Collectors.toMap(
                        // key
                        new Function<String, String>() {
                            @Override
                            public String apply(String s) {
                                return s.split(",")[0];
                            }
                        },
                        // value
                        new Function<String, Integer>() {
                            @Override
                            public Integer apply(String s) {
                                return Integer.parseInt(s.split(",")[1]);
                            }
                        }));
        System.out.println(map);

        
        System.out.println("--------收集为Map集合:Lambda表达式方式--------");
        Map<String, Integer> map1 = list.stream()
                .filter(s -> Integer.parseInt(s.split(",")[1]) >= 24)
                .collect(Collectors.toMap(
                        // key
                        s -> s.split(",")[0],
                        // value
                        s -> Integer.parseInt(s.split(",")[1])));

        System.out.println(map1);
    }
}

image-20231225221626646

6.3 练习3

  • 案例需求

    现在有两个ArrayList集合,分别存储6名男演员的名字和年龄以及6名女演员的名字和年龄。
    姓名和年龄中间用逗号隔开。
    比如:张三,23
    要求完成如下的操作:
    1,男演员只要名字为3个字的前两人
    2,女演员只要姓杨的,并且不要第一个
    3,把过滤后的男演员姓名和女演员姓名合并到一起
    4,将上一步的演员信息封装成Actor对象。
    5,将所有的演员对象都保存到List集合中。
    备注:演员类Actor,属性有:name,age
    男演员:  "蔡坤坤,24" , "叶齁咸,23", "刘不甜,22", "吴签,24", "谷嘉,30", "肖梁梁,27"
    女演员:  "赵小颖,35" , "杨颖,36", "高元元,43", "张天天,31", "刘诗,35", "杨小幂,33"
    
  • 代码实现

    演员类

    public class Actor {
        private String name;
        private int age;
    
        public Actor() {
        }
        
        public Actor(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        /**
         * 获取
         * @return name
         */
      public String getName() {
            return name;
      }
    
        /**
         * 设置
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * 获取
         * @return age
         */
        public int getAge() {
            return age;
        }
    
        /**
         * 设置
         * @param age
         */
        public void setAge(int age) {
            this.age = age;
        }
    
        public String toString() {
            return "Actor{name = " + name + ", age = " + age + "}";
        }
    }
    

    测试类

    public class Test3 {
        public static void main(String[] args) {
    
            //1.创建两个ArrayList集合
            ArrayList<String> manList = new ArrayList<>();
            ArrayList<String> womenList = new ArrayList<>();
            //2.添加数据
            Collections.addAll(manList, "蔡坤坤,24", "叶齁咸,23", "刘不甜,22", "吴签,24", "谷嘉,30", "肖梁梁,27");
            Collections.addAll(womenList, "赵小颖,35", "杨颖,36", "高元元,43", "张天天,31", "刘诗,35", "杨小幂,33");
            //3.男演员只要名字为3个字的前两人
            Stream<String> stream1 = manList.stream()
                    .filter(s -> s.split(",")[0].length() == 3)
                    .limit(2);
            //4.女演员只要姓杨的,并且不要第一个
            Stream<String> stream2 = womenList.stream()
                    .filter(s -> s.split(",")[0].startsWith("杨"))
                    .skip(1);
            
            //5.把过滤后的男演员姓名和女演员姓名合并到一起
            //演员信息封装成Actor对象,String -> Actor对象 (类型转换,使用map中间操作方法)
    
            
            // 注意:这里只能使用一个方式,两个方式同时打开运行会报错“IllegalStateException: stream has already been operated upon or closed”
            // 因为stream1, stream2已经被操作过了,不是原来的stream1, stream2了
            /*System.out.println("-------------匿名内部类方式--------------");
            List<Actor> list = Stream.concat(stream1, stream2).map(new Function<String, Actor>() {
                @Override
                public Actor apply(String s) {
                    String name = s.split(",")[0];
                    int age = Integer.parseInt(s.split(",")[1]);
                    return new Actor(name, age);
                }
            }).collect(Collectors.toList());
            System.out.println(list);*/
    
            
            System.out.println("-------------Lambda表达式方式--------------");
    
            List<Actor> list1 = Stream.concat(stream1, stream2)
                    .map(s -> new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1])))
                    .collect(Collectors.toList());
            System.out.println(list1);
        }
    }
    

image-20231225225849854

文章来源:https://blog.csdn.net/m0_47114547/article/details/135444810
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。