作者:chenssy 
来源:CSDN 
原文:https://blog.csdn.net/chenssy/article/details/44102915

我们经常使用subString方法来对String对象进行分割处理,同时我们也可以使用subList、subMap、subSet来对List、Map、Set进行分割处理,但是这个分割存在某些瑕疵。

一、subList返回仅仅只是一个视图
        首先我们先看如下实例:

public static void main(String[] args) {List<Integer> list1 = new ArrayList<Integer>();list1.add(1);list1.add(2);//通过构造函数新建一个包含list1的列表 list2List<Integer> list2 = new ArrayList<Integer>(list1);//通过subList生成一个与list1一样的列表 list3List<Integer> list3 = list1.subList(0, list1.size());//修改list3list3.add(3);System.out.println("list1 == list2:" + list1.equals(list2));System.out.println("list1 == list3:" + list1.equals(list3));}

这个例子非常简单,无非就是通过构造函数、subList重新生成一个与list1一样的list,然后修改list3,最后比较list1 == list2?、list1 == list3?。按照我们常规的思路应该是这样的:因为list3通过add新增了一个元素,那么它肯定与list1不等,而list2是通过list1构造出来的,所以应该相等,所以结果应该是:

list1 == list2:true
list1 == list3: false

首先我们先不论结果的正确与否,我们先看subList的源码:

public List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList(this, 0, fromIndex, toIndex);}

subListRangeCheck方式是判断fromIndex、toIndex是否合法,如果合法就直接返回一个subList对象,注意在产生该new该对象的时候传递了一个参数 this ,该参数非常重要,因为他代表着原始list。

/**
     * 继承AbstractList类,实现RandomAccess接口
     */

    private class SubList extends AbstractList<E> implements RandomAccess {private final AbstractList<E> parent;    //列表private final int parentOffset;   private final int offset;int size;//构造函数SubList(AbstractList<E> parent,int offset, int fromIndex, int toIndex) {this.parent = parent;this.parentOffset = fromIndex;this.offset = offset + fromIndex;this.size = toIndex - fromIndex;this.modCount = ArrayList.this.modCount;}//set方法public E set(int index, E e) {rangeCheck(index);checkForComodification();E oldValue = ArrayList.this.elementData(offset + index);ArrayList.this.elementData[offset + index] = e;return oldValue;}//get方法public E get(int index) {rangeCheck(index);checkForComodification();return ArrayList.this.elementData(offset + index);}//add方法public void add(int index, E e) {rangeCheckForAdd(index);checkForComodification();parent.add(parentOffset + index, e);this.modCount = parent.modCount;this.size++;}//remove方法public E remove(int index) {rangeCheck(index);checkForComodification();E result = parent.remove(parentOffset + index);this.modCount = parent.modCount;this.size--;return result;}}

该SubLsit是ArrayList的内部类,它与ArrayList一样,都是继承AbstractList和实现RandomAccess接口。同时也提供了get、set、add、remove等list常用的方法。但是它的构造函数有点特殊,在该构造函数中有两个地方需要注意:
        1、this.parent = parent;而parent就是在前面传递过来的list,也就是说this.parent就是原始list的引用。

2、this.offset = offset + fromIndex;this.parentOffset = fromIndex;。同时在构造函数中它甚至将modCount(fail-fast机制)传递过来了。

我们再看get方法,在get方法中return ArrayList.this.elementData(offset + index);这段代码可以清晰表明get所返回就是原列表offset + index位置的元素。同样的道理还有add方法里面的:

parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
        remove方法里面的

E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
        诚然,到了这里我们可以判断subList返回的SubList同样也是AbstractList的子类,同时它的方法如get、set、add、remove等都是在原列表上面做操作,它并没有像subString一样生成一个新的对象。所以subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上。

那么从这里的分析我们可以得出上面的结果应该恰恰与我们上面的答案相反:

list1 == list2:false
list1 == list3:true
Java细节(3.1):subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上

二、subList生成子列表后,不要试图去操作原列表
        从上面我们知道subList生成的子列表只是原列表的一个视图而已,如果我们操作子列表它产生的作用都会在原列表上面表现,但是如果我们操作原列表会产生什么情况呢?

public static void main(String[] args) {List<Integer> list1 = new ArrayList<Integer>();list1.add(1);list1.add(2);//通过subList生成一个与list1一样的列表 list3List<Integer> list3 = list1.subList(0, list1.size());//修改list3list1.add(3);System.out.println("list1'size:" + list1.size());System.out.println("list3'size:" + list3.size());}

该实例如果不产生意外,那么他们两个list的大小都应该都是3,但是偏偏事与愿违,事实上我们得到的结果是这样的:

list1'size:3
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$SubList.checkForComodification(Unknown Source)
    at java.util.ArrayList$SubList.size(Unknown Source)
    at com.chenssy.test.arrayList.SubListTest.main(SubListTest.java:17)

list1正常输出,但是list3就抛出ConcurrentModificationException异常,看过我另一篇博客的同仁肯定对这个异常非常,fail-fast?不错就是fail-fast机制,在fail-fast机制中,LZ花了很多力气来讲述这个异常,所以这里LZ就不对这个异常多讲了(更多请点这里:Java提高篇(三四)—–fail-fast机制)。我们再看size方法:

public int size() {checkForComodification();return this.size;}size方法首先会通过checkForComodification验证,然后再返回this.size。private void checkForComodification() {if (ArrayList.this.modCount != this.modCount)throw new ConcurrentModificationException();}

该方法表明当原列表的modCount与this.modCount不相等时就会抛出ConcurrentModificationException。同时我们知道modCount 在new的过程中 “继承”了原列表modCount,只有在修改该列表(子列表)时才会修改该值(先表现在原列表后作用于子列表)。而在该实例中我们是操作原列表,原列表的modCount当然不会反应在子列表的modCount上啦,所以才会抛出该异常。

对于子列表视图,它是动态生成的,生成之后就不要操作原列表了,否则必然都导致视图的不稳定而抛出异常。最好的办法就是将原列表设置为只读状态,要操作就操作子列表:

//通过subList生成一个与list1一样的列表 list3
List<Integer> list3 = list1.subList(0, list1.size());
        
//对list1设置为只读状态
list1 = Collections.unmodifiableList(list1);
Java细节(3.2):生成子列表后,不要试图去操作原列表,否则会造成子列表的不稳定而产生异常

三、推荐使用subList处理局部列表
        在开发过程中我们一定会遇到这样一个问题:获取一堆数据后,需要删除某段数据。例如,有一个列表存在1000条记录,我们需要删除100-200位置处的数据,可能我们会这样处理:

for(int i = 0 ; i < list1.size() ; i++){
   if(i >= 100 && i <= 200){
       list1.remove(i);
       /*
        * 当然这段代码存在问题,list remove之后后面的元素会填充上来,
         * 所以需要对i进行简单的处理,当然这个不是这里讨论的问题。
         */
   }
}
        这个应该是我们大部分人的处理方式吧,其实还有更好的方法,利用subList。在前面LZ已经讲过,子列表的操作都会反映在原列表上。所以下面一行代码全部搞定:

list1.subList(100, 200).clear();
简单而不失华丽!!!!!

Java集合subList使用注意事项相关推荐

  1. Java 集合(List、Set)与数组(Arr)互转方法及注意事项,难怪《阿里开发手册》会如此重视

    原创博文,欢迎转载,转载时请务必附上博文链接,感谢您的尊重. 前言: 如果你现在正急于解决问题,请立刻.马上拉到本篇最后的[总结],进入"不废话环节". 通过本篇的学习,你将全面掌 ...

  2. Java集合篇:集合细节:为集合指定初始容量、asList的缺陷、subList的缺陷

    一.为集合指定初始容量: 集合是我们在Java编程中使用非常广泛的,它就像大海,海纳百川,像万能容器,盛装万物,而且这个大海,万能容器还可以无限变大(如果条件允许).当这个海.容器的量变得非常大的时候 ...

  3. Java OOP 7 JAVA 集合框架

    Java OOP 第七章 JAVA 集合框架 文章目录 Java OOP 第七章 JAVA 集合框架 一.学习目标 二.数组存在的缺陷 三.Java集合框架 四.Collection接口 五.Coll ...

  4. Java集合、IO、多线程的一些知识

    Java集合 集合总图概览 Collection Collection 分为List<E>接口和Set<E>接口. 共有的常见方法 添加: boolean add(Object ...

  5. 手撕Java集合源码——list集合

    Java集合(一)--LinkedList和ArrayList源码 一.集合 集合主要分为两组(单列集合,双列集合) 单列集合:存放单个元素 Collection:两个重要的接口 List Set A ...

  6. java 集合 接口_Java集合之Collection接口

    1 - Java集合介绍 /* 1. 一方面, 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象 的操作,就要对对象进行存储. 2. 另一方面,使用Array存储对象方面具有一些弊 端,而 ...

  7. 【Java集合框架】ArrayList类方法简明解析(举例说明)

    本文目录 1.API与Java集合框架 2.ArrayList类方法解析 2.1 add() 2.2 addAll() 2.3 clear() 2.4 clone() 2.5 contains() 2 ...

  8. Java基础篇:Java集合

    文章目录 1.概述 2.Collection接口 2.1 Collection接口方法 2.2 Iterator迭代器接口 2.3 Collection子接口之:List接口 2.4 Collecti ...

  9. Java 集合系列02之 Collection架构

    概要 首先,我们对Collection进行说明.下面先看看Collection的一些框架类的关系图: Java 集合系列02之 Collection架构 Collection是一个接口,它主要的两个分 ...

最新文章

  1. python tkinter 布局_(4)python tkinter-布局
  2. C语言学习笔记--预编译/宏定义/数组/参数传递/函数指针
  3. 动态生成控件的消息处理
  4. 学习PHP时的一些总结(五)
  5. leetcode35. 搜索插入位置(二分搜索)
  6. h5文字垂直居中_CSS中垂直居中和水平垂直居中的方法
  7. android百度地图例子,使用百度地图API进行Android地图应用开发(Eclipse)(示例代码)...
  8. python函数介绍
  9. 华为麦芒9正式亮相:6400万三摄,2199元起
  10. 基于Ajax的应用程序架构汇总(一) (转自CSDN)
  11. java8 foreach 伟参_【java8】为java8的foreach正名
  12. python机器学习库keras——线性回归、逻辑回归、一般逻辑回归
  13. 【并查集】NOI2015 洛谷 P1955 程序自动分析
  14. JavaSE学习--HTML
  15. uniapp进行H5微信支付
  16. 计算机管理规划,2019年计算机软考系统规划与管理师考试大纲
  17. NDT 公式推导及源码解析(1)
  18. 为老婆准备的200000余条笑话,天天开心…
  19. SAP云上自适应跨可用区高可用方案
  20. netca/dbca方框乱码的解决方法

热门文章

  1. 彻底关闭Win10自动更新(转载)
  2. 股票策略 —— 指数择时 + 因子选股1
  3. 计算机主机部件在机箱中的位置示意图,一种计算机主机机箱的制作方法
  4. iOS输入表情后系统异常
  5. VMware Workstation——虚拟机更换磁盘文件路径的方法总结
  6. WINDOW -- m2硬盘装win10系统遇到的问题
  7. 树、森林与二叉树的相互转换和遍历
  8. 基于SpringBoot+ Spring Data Jpa的后台管理系统【源码开源】
  9. crypto-js加密算法库【安装教程、缓存加密】
  10. Manjaro下Steam无法启动