首页 > 编程语言 >Java-集合类-Arrays.asList()和subList使用需要注意的大坑

Java-集合类-Arrays.asList()和subList使用需要注意的大坑

时间:2024-06-16 14:03:09浏览次数:25  
标签:Java Arrays List list subList 列表 asList

Arrays.asList和subList使用需要注意的大坑


Arrays.asList() 是 Java 中一个常用的方法,它 用于将数组转换为列表(List)。这个方法非常方便,但也有一些 需要注意的“大坑”

一、Java-集合类-Arrays.asList()

大坑

1、不可修改列表大小&&原始数组与列表共享数据

在这里插入图片描述

Arrays.asList()的源码可知转换后的列表是固定大小,这意味着你不能增加或删除元素,但可以修改现有元素(如果它们是可变对象的话)。转换后得到的列表与原始数组共享相同的底层数组。这意味着对列表的修改会影响到原始数组,反之亦然

  • 不可修改列表元素的类型
Integer[] array = {1, 2, 3};
List<Integer> list = Arrays.asList(array);
// 下面这行代码会抛出 UnsupportedOperationException
list.add(4); // 错误:无法添加元素
  • 原始数组与列表共享数据
Integer[] array = {1, 2, 3};
List<Integer> list = Arrays.asList(array);
list.set(0, 99); // 修改列表的第一个元素
System.out.println(array[0]); // 输出 99,因为数组也被修改了

2、对于基本类型数组的使用限制

​ 如果你尝试用基本类型数组(如 int[])调用 Arrays.asList(),结果并不是你期望的列表,而是一个包含单个元素(即整个数组本身)的列表

int[] primitiveArray = {1, 2, 3};
List<int[]> list = Arrays.asList(primitiveArray);
// list 现在是包含一个元素的列表,这个元素是原始数组 primitiveArray

两个错误案例

wrong1

private static void wrong1() {
    int[] arr = {1, 2, 3};
    List list = Arrays.asList(arr);
    log.info("list:{} size:{} class:{}", list, list.size(), list.get(0).getClass());
}

运行结果:

在这里插入图片描述

​ 按道理输出结果list.size()也应该等于3才对,实际输出了个很奇怪的结果,我们从上诉“大坑2”可知,这里用基本类型数组调用Arrays.asList()了,所以得到的并不是期望的结果

正确方式:

private static void right1() {
    int[] arr1 = {1, 2, 3};
    List list1 = Arrays.stream(arr1).boxed().collect(Collectors.toList());
    log.info("list:{} size:{} class:{}", list1, list1.size(), list1.get(0).getClass());

    Integer[] arr2 = {1, 2, 3};
    List list2 = Arrays.asList(arr2);
    log.info("list:{} size:{} class:{}", list2, list2.size(), list2.get(0).getClass());
}
  • Arrays.stream(arr1)将数组转换为流(Stream),boxed()将流中的int值包装为Integer对象,collect(Collectors.toList())将流收集到一个新的List列表中
  • 最好直接用包装类:Integer[] arr2

运行结果:

在这里插入图片描述

wrong2

private static void wrong2() {
    String[] arr = {"1", "2", "3"};
    List list = Arrays.asList(arr);
    arr[1] = "4";
    try {
        list.add("5");
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    log.info("arr:{} list:{}", Arrays.toString(arr), list);
}

运行结果:

在这里插入图片描述

​ 直接报错。由“大坑1”可知,此处直接往转换后的list中,添加数据,所以直接添加失败

正确方式:

private static void right2() {
    String[] arr = {"1", "2", "3"};
    List list = new ArrayList(Arrays.asList(arr));
    arr[1] = "4";
    try {
        list.add("5");
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    log.info("arr:{} list:{}", Arrays.toString(arr), list);
}

运行结果:

在这里插入图片描述

  • 直接重新new一个ArrayList对象,开辟新的空间即可

二、Java-集合类-list.subList

​ subList方法是Java中List接口的一个成员方法,用于从现有的列表中获取一个子列表视图,这个视图包含了原列表中指定范围的元素。具体来说,该方法的签名如下:

public List<E> subList(int fromIndex, int toIndex)
  • 参数说明:

    • fromIndex: 子列表的起始位置(包含)。这个索引必须是非负的,并且小于toIndex。
    • toIndex: 子列表的结束位置(不包含)。这个索引必须是非负的,并且不大于列表的大小。
  • 返回值: 返回一个新的列表视图,包含原列表中从fromIndex(包括)到toIndex(不包括)位置的元素

注意事项

  • 共享数据: subList返回的列表是一个视图,它与原列表共享相同的底层数据结构。这意味着对子列表的修改会影响到原列表,反之亦然。
  • 不可变性: 虽然子列表是可修改的(可以添加、删除元素等),但这些修改会反映到原列表中,因此原列表的结构并不是不变的。
  • 异常处理:
    • 如果试图通过子列表修改原列表大小(如在子列表的边界外添加或删除元素),可能会导致UnsupportedOperationException。
    • 在迭代子列表时,如果原列表被其他线程修改,可能会抛出ConcurrentModificationException。
  • 类型转换: 不能将subList的结果强制转换为ArrayList,因为实际返回的是一个内部类,如RandomAccessSubList,这样的转换会导致ClassCastException。
  • 性能影响: 对于支持快速随机访问(如ArrayList)的列表,subList性能较好;但对于不支持快速随机访问的列表(如LinkedList),频繁的子列表操作可能会影响性能。
  • 边界检查: 在调用subList时,如果索引超出范围,会抛出IndexOutOfBoundsException。
    不支持序列化: subList返回的对象通常不支持序列化,尝试序列化可能会失败。

大坑

1、ConcurrentModificationException

1)错误方式

    private static void wrong() {
        List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
        List<Integer> subList = list.subList(1, 4);
        System.out.println(subList);
        subList.remove(1);
        System.out.println(list);
        //list和subList维护的是同一个列表对象,并且该列表对象维护了一个modCount,
        //当modCount和expectedModCount不相等时,抛出ConcurrentModificationException异常
        list.add(0);    //会修改同一个列表对象的modCount,导致抛出ConcurrentModificationException异常
        try {
            subList.forEach(System.out::println);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

在这里插入图片描述

​ 这段代码将会引发 java.util.ConcurrentModificationException 异常。这是因为在这个场景中,当对list进行修改(通过list.add(0))后,再尝试遍历其子列表subList,根据Java的fail-fast机制,集合在迭代过程中检测到modCount被修改,则会抛出此异常以防止并发修改导致的数据不一致性问题。

2)正确方式1:

    private static void right1() {
        List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
        List<Integer> subList = new ArrayList<>(list.subList(1, 4));
        System.out.println(subList);
        subList.remove(1);
        System.out.println(list);
        list.add(0);
        subList.forEach(System.out::println);
    }

通过创建子列表的新实例new ArrayList<>(list.subList(1, 4))避免了这一问题,因为这样操作后,对原列表的修改不会影响到子列表的迭代,从而避免了异常抛出

3)正确方式2:

    private static void right2() {
        List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
        List<Integer> subList = list.stream().skip(1).limit(3).collect(Collectors.toList());
        System.out.println(subList);
        subList.remove(1);
        System.out.println(list);
        list.add(0);
        subList.forEach(System.out::println);
    }
  • 避免了并发修改异常:通过使用stream().skip(1).limit(3).collect(Collectors.toList())来创建子列表,实际上是创建了原列表的一个独立副本。这意味着对subList的修改或对list的修改互不影响,因此在调用list.add(0)之后,遍历subList不会抛出ConcurrentModificationException异常,这一点与right1()函数的解决方案相似。
  • 实现方式不同:虽然两者都有效避免了并发修改异常,但right2()采用了Stream API中的skip()和limit()方法来切片列表,这种方式更加灵活且表达意图更清晰,它不需要像right1()那样显式地复制子列表

2、OOM

1)错误方式

    private static void oom() {
        for (int i = 0; i < 100000; i++) {
            List<Integer> rawList = IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList());
            data.add(rawList.subList(0, 1));
        }
    }

在这里插入图片描述

结果:堆空间不够了,直接宕机了

  • 大量创建大列表: 在每次循环中,都通过IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList())创建一个含有一百万个元素的Integer列表。这个操作本身非常消耗内存但是也不至于直接报OOM,这里只是为更好看到效果
  • 子列表引用问题: 虽然每次循环只取这个大列表的前一个元素作为子列表rawList.subList(0, 1)加入到另一个列表data中,但是subList方法返回的是原列表的一个视图(view),这意味着它并不真正复制数据,而是保留了对原列表的引用。因此,即使只是添加了每个大列表的一个小片段到data中,但由于这些子列表仍然引用着它们对应的大型原始列表,导致大量内存无法被垃圾回收,最终引发内存溢出

2)正确方式

    private static void oomfix() {
        for (int i = 0; i < 100000; i++) {
            List<Integer> rawList = IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList());
            data.add(new ArrayList<>(rawList.subList(0, 1)));
        }
    }
  • 直接将所需的元素复制到一个新的列表中,而不是使用子列表,这样可以切断新列表与原大列表之间的引用关系

标签:Java,Arrays,List,list,subList,列表,asList
From: https://blog.csdn.net/kdzandlbj/article/details/139658454

相关文章

  • Java中栈(Stack)和队列(Queue)有什么区别?如何实现栈和队列?
    在计算机科学中,栈(Stack)和队列(Queue)是两种基础且广泛使用的数据结构,它们在算法设计和系统开发中扮演着重要角色。本文将深入探讨这两种数据结构的基本概念、操作方式以及在Java中的实现。栈:后进先出(LIFO)栈是一种遵循后进先出(LastInFirstOut,LIFO)原则的数据结构。在栈中,最......
  • 解锁Java高效并发:newFixedThreadPool深度剖析与实战
    1.引言在Java的并发编程中,线程池是一个重要的概念。而newFixedThreadPool作为Java标准库java.util.concurrent中Executors类的一个静态方法,为开发者提供了一个固定大小的线程池实现。本文旨在深入剖析newFixedThreadPool的原理、源码实现以及最佳实践,更好地理解和应用它。......
  • Java变量,环境添加
    此文章是专门为微云工具箱-我的世界-联机教程里面而做的软件下载微云工具箱正文开始;第一步去Java官网下载,Java1.        敲黑板!!这一步有很多小白找的地址不对,所以我把历史版本下载地址放这里Java22{最新版本下载}https://download.oracle.com/java/22/late......
  • java学习02
    注释单行注释//多行注释/..../标识符标识符只能以大小写字母和美元符号还有下划线开头但是能以以上和数字组成大小写敏感数据类型基本数据类型整数型byte一个字节short两个字节int四个字节long八个字节常量要在结尾加L或l浮点型float四个字节常量要在结尾加F或fd......
  • JAVA基础30连
    1重载和重写的区别重载:发生在同一个类中,方法名必须相同(同名不同参),参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以相同也可以不同,发生在编译时。重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于......
  • Java学习 - MySQL数据库中提到的 视图 是什么? 如何使用?
    视图是什么视图是一张虚拟的表,视图本质上保存的是SQL语句,而不是实际的数据当使用视图时,视图会根据保存的SQL语句动态生成虚拟的数据表视图的优点保密性好简化操作修改限制视图的语法创建视图CREATEVIEWIFNOTEXISTS视图名AS查询语句CREATEVIEWmyviewASSE......
  • Java学习 - MySQL数据库中 变量 和 流程控制 实例
    变量变量分类系统变量全局变量:对于服务器所有的连接有效会话变量:只在当前连接有效自定义变量用户变量:只在当前连接有效局部变量:仅在BEGIN-END中有效系统变量查看所有的系统变量SHOWGLOBAL|SESSIONVARIABLES;查看某些的系统变量SHOWGLOBAL|SESSION......
  • Java学习 - MySQL对于数据库、表、数据类型的定义
    对于数据库的定义创建库CREATEDATABASEIFNOTEXISTS库名DEFAULTCHARACTERSETutf8//设置默认字符集为utf8COLLATEuf8_general_ci;//不区分大小写caseinsensitiveCREATEDATABASEIFNOTEXISTS库名DEFAULTCHARACTERSETutf8//设置默认字......
  • Java毕业设计-基于springboot开发的图书个性化推荐系统设计与实现-毕业论文(附毕设源代
    文章目录前言一、毕设成果演示(源代码在文末)二、毕设摘要展示1、开发说明2、需求/流程分析3、系统功能结构三、系统实现展示1、前台首页功能模块2、管理员功能模块3、学生功能模块四、毕设内容和源代码获取总结Java毕业设计-基于springboot开发的图书个性化推荐系统......
  • Java毕业设计-基于springboot开发的图书管理系统-毕业论文(附毕设源代码)
    文章目录前言一、毕设成果演示(源代码在文末)二、毕设摘要展示1、开发说明2、需求/流程分析3、系统功能结构三、系统实现展示1、个人中心2、管理员管理3、用户管理4、图书出版社管理5、公告类型管理6、所在书架管理7、图书类型管理8、论坛管理9、公告信息管理10、图书信息......