不可变集合与stream流

一、不可变集合(JDK9新增)

何为不可变,不可变集合就是不能被改变的集合(添加删除修改均不可以)。那么如何创建不可变集合呢?在我们学习的单列集合List、Set以及双列集合Map中的of方法都可以创建一个不可变集合。下面是代码演示:

        //List集合创建不可变集合
        List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
        System.out.println(list);

        //Set集合创建不可变集合
        Set<Integer> set = Set.of(4, 5, 6, 7, 8, 9);
        System.out.println(set);

        //Map集合创建不可变集合
        Map<String, String> map= Map.of("name", "zs", "age", "20");
        System.out.println(map);

运行结果如下:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4]
{age=20, name=zs}

如果对里面元素进行增删该操作,均报java.lang.UnsupportedOperationException(不支持操作)异常,不能对其中的元素进行操作。

        list.add(6);

        set.remove(5);

        map.replace("name","ls");
        /*Exception in thread "main" java.lang.UnsupportedOperationException
        at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
        at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)*/

但是在创建的时候,Set与Map两个集合有需要注意的几个细节点。我们对两个集合分别介绍:

1.使用Set创建不可变集合时,要注意由于Set集合不能存放重复元素,在创建时不要写入重复元素,那么不小心写入呢?会有什么事情呢?接下来代码演示:

        //java.lang.IllegalArgumentException: duplicate element: 9
        Set<Integer> set = Set.of(4, 5, 6, 7, 8, 9,9);
        System.out.println(set);

如上会报参数重复异常,所以在创建的时候需要注意。

2.在使用Map创建不可变集合时,首先由于和Set一样不可存放重复元素,所以不要存放重复元素。其次,由于Map特殊的结构寸的是键值对,方法中的可变参数只能在形参的最后(也就是不能有两个可变参数)所以说of方法中存放的键值对是有限制的(最多存10对,如下源代码所示)。

static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
                               K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) {
        return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                                               k6, v6, k7, v7, k8, v8, k9, v9, k10, v10);
    }

如果我们非要存多个呢?就没有方法了吗?Java在设计类的时候肯定考虑到了,既然不能单个单个存,那我们就把键值对当成对象存入不就可以了,可以使用Java中的ofEntries()方法进行创建,如下是代码演示。

        Map<String,String> map = new HashMap<>();
        map.put("lsh","郑州");
        map.put("zs","上海");
        map.put("ls","北京");
        map.put("ww","苏州");
        map.put("mjy","南京");
        map.put("lsl","南阳");
        map.put("ly","广州");
        map.put("wsh","日照");
        map.put("zhw","重庆");
        map.put("wk","乌鲁木齐");
        map.put("zzg","平顶山");
        
        Map<Object, Object> map1 = Map.ofEntries(map.entrySet().toArray(new Map.Entry[0]));
        System.out.println(map1);

由于上面操作过于繁琐,且不易记忆故而JDK在10版本中添加了copyOf()方法,简化上述操作。

二、Stream(JDK1.8新增的)

(一)流的作用

结合lambda表达式,简化集合、数组的操作

(二)使用步骤

1.创建一个流对象,并把数据放进去

2.调用流中的中间方法进行操作

3.调用流中的终结方法结束操作

(三)创建流对象

获取方式方法说明
单列集合default Stream<E> stream()Collection接口中的默认方法
双列集合没有方法直接获取
数组public static <T> Stream<T> stream(T[] array)

Arrays工具类中的静态方法

离散数据public static<T> Stream<T> of(T... values)Stream接口中的静态方法

下面是代码实现

        //1.单列集合
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"a","b","c","d","e");
        Stream<String> stream1 = list.stream();
        /*stream1.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.print(s+"\t");
            }
        });*/
        //终结流方法     方法引用(JDK8新特性)
        stream1.forEach(System.out::println);
        System.out.println("-----------------------------------");

        //2.数组创建stream流
        String[] strArr = {"1","2","3","4","5","6","7","8","9"};
        Stream<String> stream2 = Arrays.stream(strArr);
        stream2.forEach(s -> System.out.print(s+"\t"));
        System.out.println();
        System.out.println("----------------------------------");


        //3.离散的数据创建stream流
        Stream<? extends Serializable> stream3 = Stream.of(1, "acb", 'A', 4, 5);
        stream3.forEach(System.out::println);
        System.out.println("----------------------------------");

        //4.双列集合(先将双列转为单列)
        Map<String,Integer> map = new HashMap<>();
        map.put("a",111);
        map.put("b",222);
        map.put("c",333);
        map.keySet().stream().forEach(System.out::println);
        map.entrySet().stream().forEach(System.out::println);

以上通过不同类型进行创建Stream流,并通过forEach()终结流方法进行打印输出。其中还用到了方法的引用(即::双冒号)、lambda表达式,这些都是JDK1.8新增的,其中值得注意的是双列集合,要先将双列集合转为单列在进行后续操作。不过其中还值得注意的是离散数据创建Stream流的方法public static<T> Stream<T> of(T... values),其中的参数是可变参数,而可变参数的底层实现是数组,那么我们可不可以试试数组呢?当然可以,你尝试后就会有新奇的发现,当你传入的是基本数据类型数组时,输出的是地址值,而传入引用数据类型数组则正常。如下是代码演示:

        //public static <T> Stream<T> of(     @NotNull  T... values ) 注意细节
        int[] arr1 = {1,2,3,4};
        String[] arr2 = {"1","2","3","4"};
        Stream.of(arr1).forEach(System.out::println);
        Stream.of(arr2).forEach(System.out::println);

运行结果:

[I@568db2f2
1
2
3
4

如上可知,要是有该方法且传入数组时要使用引用数据类型的数组。

(四)流的中间方法

常用的几个中间方法

名称说明
Stream<T> filter(Predicate<? super T> predicate)过滤
Stream<T> limit(long maxSize)获取前几个
Stream<T> skip(long n)跳过几个获取剩余的
Stream<T> distinct()元素去重,依赖hashCode和equals方法
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)合并两个Stream流
<R> Stream<R> map(Function<? super T, ? extends R> mapper)转化流中的数据类型

下面是对这几种方法的代码演示:

        ArrayList<String> lists = new ArrayList<>();
        Collections.addAll(lists,"张三丰","周芷若","张无忌","赵敏","张强","张良","王二麻子");

        //1.filter过滤
        /*lists.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.startsWith("张");
            }
        });*/
        //链式编程、lambda表达式、方法的引用
        //过滤姓张且字符长度为3
        lists.stream()
                .filter(s -> s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(System.out::println);
        System.out.println("lists原来的集合:"+lists);
        System.out.println("================================================================");


        //2.limit获取前几个
        //获取前三个
        lists.stream().limit(3).forEach(System.out::println);
        System.out.println("================================================================");


        //3.skip跳过前几个
        //跳过前三个
        lists.stream().skip(3).forEach(System.out::println);
        System.out.println("================================================================");


        //4.distinct去重
        //去除重复的
        ArrayList<String> lists2 = new ArrayList<>();
        Collections.addAll(lists2,"张三丰","张三丰","张三丰","周芷若","张无忌","赵敏","张强","张良","王二麻子");
        lists2.stream().distinct().forEach(System.out::println);
        System.out.println("================================================================");


        //5.concat合并两个流
        Stream.concat(lists.stream(),lists2.stream()).forEach(System.out::println);
        System.out.println("================================================================");


        //6.map转化数据类型
        ArrayList<String> lists3 = new ArrayList<>();
        Collections.addAll(lists3,"张三丰-21","周芷若-20","张无忌-18","赵敏-13","张强-53","张良-23","王二麻子-100");
        /*lists3.stream().map(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                String[] arr = s.split("-");
                String s1 = arr[1];
                int age = Integer.parseInt(s1);
                return age;
            }
        }).forEach(System.out::println);*/
        //输出字符后的年龄
        lists3.stream().map(s -> Integer.parseInt(s.split("-")[1])).forEach(System.out::println);

总结一下:

1.limit()、skip()、distinct()、concat()这几个方法逻辑在底层已经定义好了,直接使用即可。

2.filter()、map()而剩余的几个,则需要重写其中函数接口的方法,根据自己的需求进行不同的实现。

(五)流的终结方法

常用的终结方法

名称说明
oid forEach(Consumer<? super T> action)遍历
long count()计数
toArray()收集流中数据,放到数组中
collect(Collector collector)收集流中数据,放到集合中

如下是对前三个代码演示:

        //1.forEach()遍历
        ArrayList<String> lists = new ArrayList<>();
        Collections.addAll(lists,"张三丰","周芷若","张无忌","赵敏","张强","张良","王二麻子");

        //遍历该集合
        /*lists.stream().forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/
        /*lists.forEach(System.out::println);
        lists.stream().forEach(System.out::println);*/

        //2.count()统计
        long count = lists.stream().count();
        System.out.println(count);//7
        System.out.println("===================================================");


        //3.toArray()收集
        //IntFunction接口创建一个可以存放流中数据的数组
        //toArray将流中数据存入数组
        /*String[] arr = lists.stream().toArray(new IntFunction<String[]>() {
            @Override
            public String[] apply(int value) {//Begin size 7 is not equal to fixed size 1
                return new String[value];
            }
        });
        System.out.println(Arrays.toString(arr));*/

//        lists.stream().toArray(value -> new String[value]);
        String[] arr = lists.stream().toArray(String[]::new);
        System.out.println(Arrays.toString(arr));

下面是对将数据收集到集合中,代码演示:

        ArrayList<String> lists = new ArrayList<>();
        Collections.addAll(lists,"张三丰-21","周芷若-20","张无忌-18","赵敏-13","张强-53","张良-23","王二麻子-100");
        //输出年龄小于20的,并收集到List集合中
        List<String> list = lists.stream()
                .filter(s -> Integer.parseInt(s.split("-")[1]) < 20)
                .collect(Collectors.toList());
        System.out.println(list);
        System.out.println("====================================================================================================");

        //输出年龄小于20的,并收集到Set集合中
        Set<String> set = lists.stream()
                .filter(s -> Integer.parseInt(s.split("-")[1]) < 20)
                .collect(Collectors.toSet());
        System.out.println(set);
    System.out.println("====================================================================================================");

        //输出年龄小于20的,并收集到Map集合中
        /**
         * toMap方法中形参:第一个是键生成的规则
         *                 第二个是值生成的规则
         * 形参一:Function<String, String>
         *        第一个泛型:是流中数据的类型
         *        第二个泛型:是生成的键的类型
         *        apply()方法
         *              形参:流中的每个数据
         *              方法体:生成键的代码
         *              返回值:生成的键
         *形参二:Function<String, String>
         *        第一个泛型:是流中数据的类型
         *        第二个泛型:是生成的值的类型
         *        apply()方法
         *              形参:流中的每个数据
         *              方法体:生成值的代码
         *              返回值:生成的值
         */
        Map<String, Integer> map = lists.stream()
                .filter(s -> Integer.parseInt(s.split("-")[1]) < 20)
                //键的规则,值的规则
                .collect(Collectors.toMap(new Function<String, String>() {

                    @Override
                    public String apply(String s) {
                        return s.split("-")[0];
                    }
                }, new Function<String, Integer>() {
                    @Override
                    public Integer apply(String s) {
                        return Integer.parseInt(s.split("-")[1]);
                    }
                }));
        System.out.println(map);
        System.out.println("====================================================================================================");
        lists.stream()
                .filter(s -> Integer.parseInt(s.split("-")[1]) < 20)
                .collect(Collectors.toMap(s -> s.split("-")[0],s->Integer.parseInt(s.split("-")[1])));

流总的来说就是对集合进行简化操作的,其中里面涉及了许多函数式接口,有兴趣的可以对这部分进行学习一些,可能对你理解流会有一定帮助。

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值