我们经常使用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);
    </span><span style="color: #008000">//</span><span style="color: #008000">通过构造函数新建一个包含list1的列表 list2</span>
List&lt;Integer&gt; list2 = <span style="color: #0000ff">new</span> ArrayList&lt;Integer&gt;<span style="color: #000000">(list1); </span><span style="color: #008000">//</span><span style="color: #008000">通过subList生成一个与list1一样的列表 list3</span>
List&lt;Integer&gt; list3 = list1.subList(0<span style="color: #000000">, list1.size()); </span><span style="color: #008000">//</span><span style="color: #008000">修改list3</span>
list3.add(3<span style="color: #000000">); System.out.println(</span>&quot;list1 == list2:&quot; +<span style="color: #000000"> list1.equals(list2));
System.out.println(</span>&quot;list1 == list3:&quot; +<span style="color: #000000"> list1.equals(list3));
}</span></pre>

这个例子非常简单,无非就是通过构造函数、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;
    </span><span style="color: #008000">//</span><span style="color: #008000">构造函数</span>
SubList(AbstractList&lt;E&gt;<span style="color: #000000"> parent,
</span><span style="color: #0000ff">int</span> offset, <span style="color: #0000ff">int</span> fromIndex, <span style="color: #0000ff">int</span><span style="color: #000000"> toIndex) {
</span><span style="color: #0000ff">this</span>.parent =<span style="color: #000000"> parent;
</span><span style="color: #0000ff">this</span>.parentOffset =<span style="color: #000000"> fromIndex;
</span><span style="color: #0000ff">this</span>.offset = offset +<span style="color: #000000"> fromIndex;
</span><span style="color: #0000ff">this</span>.size = toIndex -<span style="color: #000000"> fromIndex;
</span><span style="color: #0000ff">this</span>.modCount = ArrayList.<span style="color: #0000ff">this</span><span style="color: #000000">.modCount;
} </span><span style="color: #008000">//</span><span style="color: #008000">set方法</span>
<span style="color: #0000ff">public</span> E set(<span style="color: #0000ff">int</span><span style="color: #000000"> index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue </span>= ArrayList.<span style="color: #0000ff">this</span>.elementData(offset +<span style="color: #000000"> index);
ArrayList.</span><span style="color: #0000ff">this</span>.elementData[offset + index] =<span style="color: #000000"> e;
</span><span style="color: #0000ff">return</span><span style="color: #000000"> oldValue;
} </span><span style="color: #008000">//</span><span style="color: #008000">get方法</span>
<span style="color: #0000ff">public</span> E get(<span style="color: #0000ff">int</span><span style="color: #000000"> index) {
rangeCheck(index);
checkForComodification();
</span><span style="color: #0000ff">return</span> ArrayList.<span style="color: #0000ff">this</span>.elementData(offset +<span style="color: #000000"> index);
} </span><span style="color: #008000">//</span><span style="color: #008000">add方法</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> add(<span style="color: #0000ff">int</span><span style="color: #000000"> index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset </span>+<span style="color: #000000"> index, e);
</span><span style="color: #0000ff">this</span>.modCount =<span style="color: #000000"> parent.modCount;
</span><span style="color: #0000ff">this</span>.size++<span style="color: #000000">;
} </span><span style="color: #008000">//</span><span style="color: #008000">remove方法</span>
<span style="color: #0000ff">public</span> E remove(<span style="color: #0000ff">int</span><span style="color: #000000"> index) {
rangeCheck(index);
checkForComodification();
E result </span>= parent.remove(parentOffset +<span style="color: #000000"> index);
</span><span style="color: #0000ff">this</span>.modCount =<span style="color: #000000"> parent.modCount;
</span><span style="color: #0000ff">this</span>.size--<span style="color: #000000">;
</span><span style="color: #0000ff">return</span><span style="color: #000000"> result;
}
}</span></pre>

该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);
    </span><span style="color: #008000">//</span><span style="color: #008000">通过subList生成一个与list1一样的列表 list3</span>
List&lt;Integer&gt; list3 = list1.subList(0<span style="color: #000000">, list1.size());
</span><span style="color: #008000">//</span><span style="color: #008000">修改list3</span>
list1.add(3<span style="color: #000000">); System.out.println(</span>&quot;list1'size:&quot; +<span style="color: #000000"> list1.size());
System.out.println(</span>&quot;list3'size:&quot; +<span style="color: #000000"> list3.size());
}</span></pre>

该实例如果不产生意外,那么他们两个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个建议


-----原文出自:http://cmsblogs.com/?p=1239,请尊重作者辛勤劳动成果,转载说明出处.

-----个人站点:http://cmsblogs.com

Java提高配(三七)-----Java集合细节(三):subList的缺陷的更多相关文章

  1. Java学习笔记29(集合框架三:泛型)

    泛型的概念: 简单地讲,就是同一个方法(类),可以接受不同的数据类型并运行得到相对应的结果,不会出现安全问题 上一篇有一段这样的代码: 没有定义集合类型.迭代器类型 package demo; imp ...

  2. Java学习笔记十二--集合(三)

    第一节课 返回值 方法名 作用 void add(index,elemnet) 在指定的索引处添加元素 object get(index) 返回指定索引处的元素 int indexOf(object) ...

  3. Java提高篇(三五)-----Java集合细节(一):请为集合指定初始容量

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

  4. java中遍历集合的三种方式

    第一种遍历集合的方式:将集合变为数组 package com.lw.List; import java.util.ArrayList; import java.util.List; import ja ...

  5. java 集合框架(三)Collection

    一.概述 Collection是集合框架的根接口.不同的集合具有不同的特性,比如有的集合可以有重复元素,有的不可以,有的可以排序,有的不可排序,如此等等,而Collection作为集合的根接口,它规范 ...

  6. Java基础学习笔记十七 集合框架(三)之Map

    Map接口 通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同,如下图. Collection中的集合,元素是孤立存在的(理解为单身),向集合中存 ...

  7. Java面试集合(三)

    前言 大家好,给大家带来Java面试集合(三)的概述,希望你们喜欢 三 1.在Java中是否可以含有多个类? 答:可以含有多个类,但只有一个是public类,public类的类名与文件名必须一致. 2 ...

  8. Java遍历List集合的三种方法

    Java遍历List集合的三种方法 List<String> list = new ArrayList<String>(); list.add("aaa") ...

  9. Java集合系列(三):HashSet、LinkedHashSet、TreeSet的使用方法及区别

    本篇博客主要讲解Set接口的三个实现类HashSet.LinkedHashSet.TreeSet的使用方法以及三者之间的区别. 注意:本文中代码使用的JDK版本为1.8.0_191 1. HashSe ...

随机推荐

  1. oracle 秒

    select case when  deptno=10 then 'aaaa' when  deptno=20 then 'bbbb' when deptno=30 then 'cccc'     e ...

  2. VS 快捷键(待完善)

    本人使用的是VS2010版本的 感觉还算稳定. 快捷键: 1. Ctrl+E,U 用于对选中的代码行快速对齐: 2. Ctrl+R,E 用于对象属性的重构,比如对get,set属性的快速设置. 方法: ...

  3. JQuery初体验

    虽然做b/s也有一年半了,但是还没怎么认真的去看JQuery,趁自己生病的这几天,恶补一下JQuery方面的知识,保持学习的态度,内容很简单,聊以自慰一下>_<.废话不多说,直接上代码了. ...

  4. 【Java学习笔记】foreach语句(高级for)

    package p2; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java ...

  5. zabbix微信告警(虚拟机脚本测试成功,zabbix上收不到信息)

    前言: 使用zabbix直接运行脚本又可以正常接收.但是登录zabbix  web界面,测试!  动作显示已送达,但是微信并没有收到信息! 解决: 添加脚本参数,因为不添加脚本参数,调用不了你这个脚本 ...

  6. IP地址框

    //IP地址框 // 此段代码:独立的获取本机IP地址和计算机名 WORD wVersionRequested; WSADATA wsaData; char name[255]; CString ip ...

  7. MVC模式下向qq邮箱发送邮件

    将已经保存在数据库中的密码通过邮件发送到qq邮箱中.用的ssm框架,其中的config文件要先配置好. 用到的jar包有gson-2.2.1.jar,gson.jar,mail.jar,activat ...

  8. XML解析工具类

    public class XmlUtil { /* * 利用dom4j解析xml文件内容,并返回map数据形式 * path是.xml文件所在的路径 */ public static Map<S ...

  9. ES5 getter setter

    最近在学习vuejs,了解到内部实现使用到了es5的Getters和Setters.之前看高程的时候,没有重视这块,今天查看一下文档,了解了他们的作用,再次记录一下,可供以后查看和共享. 定义Gett ...

  10. [UCSD白板题] Take as Much Gold as Possible

    Problem Introduction This problem is about implementing an algorithm for the knapsack without repeti ...