Java_集合操作_关于subList,子列表
来源:互联网 发布:蓝牙模块设置软件 编辑:程序博客网 时间:2024/05/24 06:33
子列表只是原列表的一个视图
List接口提供了subList方法,其作用是返回一个列表的子列表,这与String类的subString有点类似,但它们的功能是否相同呢?我们来看如下代码:
package deep;import java.util.ArrayList;import java.util.List;public class Client { public static void main(String[] args) { // 定义一个包含两个字符串的列表 List<String> c = new ArrayList<String>(); c.add("A"); c.add("B"); // 构造一个包含c列表的字符串列表 List<String> c1 = new ArrayList<String>(c); // subList生成与c相同的列表 List<String> c2 = c.subList(0, c.size()); // c2增加一个元素 c2.add("C"); System.out.println("c == c1? " + c.equals(c1)); System.out.println("c == c2? " + c.equals(c2)); System.out.println(c); }}
运行结果:
c == c1? false
c == c2? true
[A, B, C]
c2是通过subList方法从c列表中生成的一个子列表,然后c2又增加了一个元素,可为什么增加了一个元素还相等呢?我们来看subList的源码:
public List<E> subList(int fromIndex, int toIndex) { return new SubList<>(this, fromIndex, toIndex); }
class SubList<E> extends AbstractList<E> { //原始列表 private final AbstractList<E> l; //偏移量 private final int offset; private int size; //构造函数,注意list参数就是我们的原始列表 SubList(AbstractList<E> list, int fromIndex, int toIndex) { if (fromIndex < 0) throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); if (toIndex > list.size()) throw new IndexOutOfBoundsException("toIndex = " + toIndex); if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); //传递原始列表 l = list; offset = fromIndex; //子列表的长度 size = toIndex - fromIndex; this.modCount = l.modCount; } public E set(int index, E element) { rangeCheck(index); checkForComodification(); return l.set(index+offset, element); } public E get(int index) { rangeCheck(index); checkForComodification(); return l.get(index+offset); } public int size() { checkForComodification(); return size; } public void add(int index, E element) { rangeCheckForAdd(index); checkForComodification(); l.add(index+offset, element); this.modCount = l.modCount; size++; } public E remove(int index) { rangeCheck(index); checkForComodification(); E result = l.remove(index+offset); this.modCount = l.modCount; size--; return result; } protected void removeRange(int fromIndex, int toIndex) { checkForComodification(); l.removeRange(fromIndex+offset, toIndex+offset); this.modCount = l.modCount; size -= (toIndex-fromIndex); } public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); int cSize = c.size(); if (cSize==0) return false; checkForComodification(); l.addAll(offset+index, c); this.modCount = l.modCount; size += cSize; return true; } public Iterator<E> iterator() { return listIterator(); } public ListIterator<E> listIterator(final int index) { checkForComodification(); rangeCheckForAdd(index); return new ListIterator<E>() { private final ListIterator<E> i = l.listIterator(index+offset); public boolean hasNext() { return nextIndex() < size; } public E next() { if (hasNext()) return i.next(); else throw new NoSuchElementException(); } public boolean hasPrevious() { return previousIndex() >= 0; } public E previous() { if (hasPrevious()) return i.previous(); else throw new NoSuchElementException(); } public int nextIndex() { return i.nextIndex() - offset; } public int previousIndex() { return i.previousIndex() - offset; } public void remove() { i.remove(); SubList.this.modCount = l.modCount; size--; } public void set(E e) { i.set(e); } public void add(E e) { i.add(e); SubList.this.modCount = l.modCount; size++; } }; }
通过阅读这段代码,我们就非常清楚subList方法的实现原理了;它返回的SubList类也是AbstractList的子类,其所有的方法如get、set、add、remove等都是在原始列表上的操作,它自身并没有生成一个数组或是链表,也就是子列表自是原列表的一个视图(View),所有的修改动作都反应在了原列表上。所以最后打印的c为[A, B, C]。
推荐使用subList处理局部列表
我们来看这样一个简单的需求:一个列表有20个元素,现在要删除索引位置为6-10的元素。代码如下:
package deep;import java.util.ArrayList;import java.util.List;public class Client { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(20); list.add(0); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); list.add(6); list.add(7); list.add(8); list.add(9); list.add(10); list.add(11); list.add(12); list.add(13); list.add(14); list.add(15); list.add(16); list.add(17); list.add(18); list.add(19); int offset = 0; for (int i = 6; i <= 10; ++i) { if (i < list.size()) { list.remove(i - offset); ++offset; } } System.out.println(list); }}
运行结果:
[0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16, 17, 18, 19]
或者:
int offset = 0; for (int i = 0, size = list.size(); i < size; ++i) { if (i > 5 && i <= 10) { list.remove(i - offset); ++offset; } }
不过,还有没有其他方式呢?有没有“one-lining”一行代码就解决问题的方式呢?
有,直接使用ArrayList的removeRange方法不就可以了吗?等等,好像不可能呀,虽然JDK上有此方法,但是它有protected关键字修饰着,不能直接使用,那怎么办?看看如下代码:
package deep;import java.util.ArrayList;import java.util.List;public class Client { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(20); list.add(0); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); list.add(6); list.add(7); list.add(8); list.add(9); list.add(10); list.add(11); list.add(12); list.add(13); list.add(14); list.add(15); list.add(16); list.add(17); list.add(18); list.add(19); // 删除指定范围的元素 list.subList(6, 11).clear(); System.out.println(list); }}
运行结果:
[0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16, 17, 18, 19]
因为subList上的所有操作都是在原始列表上进行的,那我们就用subList先取出一个子列表,然后清空。因为subList返回的List是原始列表的一个视图,删除这个视图中的所有元素,最终就会反映到原始字符串上,那么一行代码即解决问题了。
生成子列表后不要再操作原列表
在subList执行完后,如果修改了原列表的内容会怎样呢?视图是否会改变呢?如果是数据库视图,表数据变更了,视图当然会变了,至于subList生成的视图是否会改变,看如下代码:
package deep;import java.util.ArrayList;import java.util.List;public class Client { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("A"); list.add("B"); list.add("C"); List<String> subList = list.subList(0, 2); // 原字符串增加一个元素 list.add("D"); System.out.println("原列表长度:" + list.size()); System.out.println("子列表长度:" + subList.size()); }}
运行结果:
原列表长度:4
Exception in thread “main” java.util.ConcurrentModificationException
at java.util.ArrayList
at deep.Client.main(Client.java:17)
什么?居然是subList的size方法出现了异常,而且还是并发修改异常?这没道理呀,这里根本就没有多线程操作,何来并发修改呢?这个问题很容易回答,那是因为subList取出的列表是原列表的一个视图,原数据集(代码中的list变量)修改了,但是subList取出的子列表不会重新生成一个新列表(这点与数据库视图是不相同的),后面在对子列表继续操作时,就会检测到修改计数器与预期的不相同,于是就抛出了并发修改异常。
出现这个问题的最终原因还是在子列表提供的size方法的检查上,还记得上面几个例子中经常提到的修改计数器吗?原因就在这里,我们来看看size的源代码:
public int size() { checkForComodification(); return this.size; }
其中的checkForComodification方法就是用于检测是否并发修改的,代码如下:
private void checkForComodification() { //判断当前修改计算器是否与子列表生成时一致 if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); }
this.modCount是在SubList子列表的构造函数中赋值的,其值等于生成子列表时的修改次数,如下:
SubList(AbstractList<E> list, int fromIndex, int toIndex) { if (fromIndex < 0) throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); if (toIndex > list.size()) throw new IndexOutOfBoundsException("toIndex = " + toIndex); if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); l = list; offset = fromIndex; size = toIndex - fromIndex; //将原始列表的修改次数赋值给subList this.modCount = l.modCount; }
因此在生成子列表后再修改原始列表,ArrayList.this.modCount 必然比 this.modCount大1,不再保持相等了,于是也就抛出了ConcurrentModificationException异常。
subList的其他方法也会检测修改计数器,例如set、get、add、等方法,若生成子列表后,再修改原列表,这些方法也会抛出ConcurrentModificationException异常。
对于子列表操作,因为视图是动态生成的,生成子列表后再操作原列表,必然会导致“视图”的不稳定,最有效的方法就是通过Collections.unmodifiableList方法设置列表为只读状态,代码如下:
package deep;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;import java.util.List;public class Client { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("A"); list.add("B"); list.add("C"); List<String> subList = list.subList(0, 2); // /设置原列表为只读状态 list = Collections.unmodifiableList(list); // 原字符串增加一个元素 list.add("D"); System.out.println("原列表长度:" + list.size()); System.out.println("子列表长度:" + subList.size()); }}
这在团队编码中特别有用,防御式编程就是教我们如此做的。
这里还有一个问题,数据库的一张表可以有很多视图,我们的List也可以有多个视图,也就是可以有多个子列表,但问题是只要生成的子列表多于一个,则任何一个子列表就都不能修改了,否则就会抛出ConcurrentModificationException异常。
- Java_集合操作_关于subList,子列表
- Java_集合操作_遍历集合方法
- Java_集合操作_反转集合
- Java_集合操作_避开基本类型数组转换列表陷阱
- Java_集合操作_不同的列表选择不同的遍历方法
- Java_集合操作_使用细节
- Java_集合操作_关系图
- Java_集合操作_清空list
- Java_集合操作_合并两个map
- Java_集合操作_使集合乱序
- Java_集合操作_集合中的哈希码不要重复
- java_基础_集合
- java_集合_总结
- Java_集合操作_数组转换为List
- Java_集合操作_复制list到另一list中
- Java_集合操作_多种最值算法,适时选择
- Java_集合操作_非稳定排序推荐使用List
- Java_集合操作_集合运算时使用更优雅的方式
- 算法-简单的四则运算
- xshell连接ubuntu
- matlab坐标轴设置及其各类关于画图的函数
- 使用JXL实现对Excel文件的简单操作
- Volley框架下的三级缓存的使用工具类
- Java_集合操作_关于subList,子列表
- java内存分配和string的深度解析
- Python:数据类型之间的转换
- Linux常用命令
- scanf格式控制符的完整格式
- 栈
- 各种字符编码方式详解及由来(ANSI,UNICODE,UTF-8,GB2312,GBK)
- vagrant个人搭建自动化测试环境
- Adobe CS6 系列索引