2019独角兽企业重金招聘Python工程师标准>>>

我们经常使用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的列表 list2  List<Integer> list2 = new ArrayList<Integer>(list1);  //通过subList生成一个与list1一样的列表 list3  List<Integer> list3 = list1.subList(0, list1.size());  //修改list3  list3.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一样的列表 list3  List<Integer> list3 = list1.subList(0, list1.size());  //修改list3  list1.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程序的151个建议

转载于:https://my.oschina.net/u/3572551/blog/1544332

Java集合细节(三):subList的缺陷相关推荐

  1. java comparable接口_Java面试题之Java集合篇三

    Java面试题之Java集合篇三1.HashMap和HashTable有何不同? (1)HashMap允许key和value为null,而HashTable不允许. (2)HashTable是同步的, ...

  2. 学习笔记 java 集合(三)

    向量类 Vector 和栈类Statck类 和栈类 Statck java 框架集合是java 2中引入的.java 2 之前的版本也支持一些数据结构,其中就有向量类 Victor 与栈类 Statc ...

  3. Java核心技术基础知识学习之Java集合(三)

    文章目录 七.Java集合 7.6 Java 8 增强的 Map 集合 7.6.1 Java 8 中 Map 新增的方法 7.6.2 Java 8 改进的 HashMap 和 HashTable 实现 ...

  4. 面渣逆袭:Java集合连环三十问

    大家好,我是老三.上期发布了一篇:面渣逆袭:HashMap追魂二十三问,反响很好! 围观群众纷纷表示

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

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

  6. Java OOP 7 JAVA 集合框架

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

  7. Java集合(一)集合框架概述

    文章目录 一.集合框架 1.1 常用的集合 1.2 线程安全的集合 1.3 "fail-fast"机制 1.3.1 "fail-fast"机制是什么 1.3.2 ...

  8. Java集合(四) LinkedList详解

      在上篇文章Java集合(三) ArrayList详解的学习和源码分析中,我们知道ArrayList是以数组实现,它的优势是查询性能高,劣势是按顺序增删性能差.如果在不确定元素数量的情况时,不建议使 ...

  9. Java集合有哪些?

    Java集合简述 Java集合有哪些? 每个集合常用的实现类有哪些? 每个集合不同的实现类的区别是什么? 总结 友情提示 落款 同博主好文章 Java集合有哪些? java集合分三种,List.Set ...

最新文章

  1. Dropbox如何使用机器学习从数十亿图片中自动提取文字
  2. PL/0语言编译程序分析
  3. JZOJ 3943. 【GDOI2015模拟11.29】环游世界
  4. drive数据集_英伟达的最强人脸GAN开源了,它吃的高清数据集也开源了
  5. Dist类系列(一):根据字典的key值、value值进行排序
  6. 微信小程序的线程架构
  7. service mysqld start,Failed to start mysqld.service: Access denied
  8. 行为设计模式:中介者
  9. supervisor 守护多个进程_进程管理工具之Supervisor
  10. (转)ASP.NET Core 企业开发架构概述
  11. Google 推出 Android 11 的 Developer Preview 3 版本
  12. Atitit 法学体系树与知识点attilax大总结 法学体系 0301法学类 030101 法学理论 宪法 行政法 民法 商法 婚姻法和继承法 经济法 社会法 刑法 民事诉讼法 行政诉讼法
  13. 虚妄的奇迹,血泪的现实——记Fateamp;n…
  14. Linux安装中文输入法(Google拼音输入法)
  15. 五子棋(含较高级的人机对战)
  16. 【从零开始】手写数字识别降维可视化学习笔记
  17. stm32mp15x环境搭建基于linux环境(上)
  18. Map 和 ForEach 的区别
  19. Windows 10即将“被订阅”:关于订阅制的痛并快乐
  20. halcon裁剪图像_Halcon将裁剪后的图像还原为原始大小

热门文章

  1. 对一致性Hash算法,Java代码实现的深入研究
  2. 增大胸围!Mr Burning带你在家全方位虐胸!
  3. LaTex:算法排版
  4. 多分类问题中混淆矩阵(Confusion Matrix)的Matlab画法
  5. Hadoop集群(第6期)_WordCount运行详解
  6. GifCam:最佳免费gif动画精细录制/剪辑软件
  7. 浅谈线程池(上):线程池的作用及CLR线程池
  8. IBM与HP存储数据复制技术PK
  9. 信息安全系统设计基础第二次实验
  10. Fast Walsh-Hadamard Transform——快速沃尔什变换