再说Java集合,subList之于ArrayList
上一章说了很多ArrayList相关的内容,但还有一块儿内容没说到,那就是subList方法。先看一段代码
public static void testSubList() {
List<String> stringList = new ArrayList<>();
stringList.add("牛魔王");
stringList.add("蛟魔王");
stringList.add("鹏魔王");
stringList.add("狮驼王");
stringList.add("猕猴王");
stringList.add("禺贼王");
stringList.add("美猴王");
List<String> substrings = stringList.subList(3,5);
System.out.println(substrings.toString());
System.out.println(substrings.size());
substrings.set(1, "猪八戒");
System.out.println(substrings.toString());
System.out.println(stringList.toString());
}
看看执行结果如何?
[狮驼王, 猕猴王]
2
[狮驼王, 猪八戒]
[牛魔王, 蛟魔王, 鹏魔王, 狮驼王, 猪八戒, 禺贼王, 美猴王]
第一和第二的执行结果,非常容易理解,subList()方法作用就是截取集合stringList中一个范围内的元素。
第三和第四的执行结果都值得分析了,首先截取的字符串集合值为 [狮驼王, 猕猴王] ,但因为猕猴王在大雷音寺被美猴王打死了,我们用猪八戒来代替猕猴王;
因此我们通过substrings.set(1, "猪八戒"),将这个集合中第二个位置的值“猕猴王”设置为“猪八戒”,最终打印出来的结果也正是我们所预期的;但同时我们打印原集合stringList,发现其中的“猕猴王”也变成了“猪八戒”。这就比较奇怪了,两个问题:
1.我们操作的是截取后的集合,为什么原集合会变?
2.我们设置截取后某个位置(如第2个位置)的值,原集合改变的却不是对应位置的值?
一. subList原理初探
接下来我们带着问题寻找答案,我们看一下subList()的源码
/**
* Returns a view of the portion of this list between the specified
* {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. (If
* {@code fromIndex} and {@code toIndex} are equal, the returned list is
* empty.) The returned list is backed by this list, so non-structural
* changes in the returned list are reflected in this list, and vice-versa.
* The returned list supports all of the optional list operations.
*
* <p>This method eliminates the need for explicit range operations (of
* the sort that commonly exist for arrays). Any operation that expects
* a list can be used as a range operation by passing a subList view
* instead of a whole list. For example, the following idiom
* removes a range of elements from a list:
* <pre>
* list.subList(from, to).clear();
* </pre>
* Similar idioms may be constructed for {@link #indexOf(Object)} and
* {@link #lastIndexOf(Object)}, and all of the algorithms in the
* {@link Collections} class can be applied to a subList.
*
* <p>The semantics of the list returned by this method become undefined if
* the backing list (i.e., this list) is <i>structurally modified</i> in
* any way other than via the returned list. (Structural modifications are
* those that change the size of this list, or otherwise perturb it in such
* a fashion that iterations in progress may yield incorrect results.)
*
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
*/
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
看注释,大概有以下几个意思
- 返回的是原集合在fromIndex和toIndex之间的元素的视图,虽然为视图,但支持集合的所有方法;
- 当fromIndex和toIndex相同时,返回空的视图;
- 任何对截取的视图的操作都会被原集合所取代;
看注释仅能知道我们例子最后的运行结果是正常的,但是对原理也还并不是特别清楚。我们继续看源码。
首先我们在例子中调用subList(3, 5)时,是new了一个SubList,这个SubList是ArrayList内部类,继承了AbstractList
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;
}
}
从这个内部类的源码中,我们可以看到:
- SubList并没有像ArrayList一样定义Object[]来存放数据,而定义了一个变量parent来保存传递的原集合;
- 定义了一个offset用于保存进行偏移量,当对SubList修改时,就可以通过偏移量找到parent中对应的位置;
- 定义了size用来表示我们在parent集合中可见范围是多少;

有了上面的说明,其实SubList的原理已经很清晰了,接下来,我们用SubList中常用的方法来印证一下。
二. add(E e)方法
substrings.add("九头蛇");
System.out.println(substrings.toString());
System.out.println(stringList.toString());
接着上面的例子,在substrings中添加“九头蛇”,按照规则,add()方法添加元素会在集合的最后,也就是说substrings的第3个位置(下标为2),对应parent原集合的位置下标就是2+3=5,会在stringList第六个位置插入“九头蛇”。看一下输出的结果
[狮驼王, 猪八戒, 九头蛇]
[牛魔王, 蛟魔王, 鹏魔王, 狮驼王, 猪八戒, 九头蛇, 禺贼王, 美猴王]
可以看到结果的确如此,那么我们在看一下add(E e),在SubList这个内部类里面并没有发现该方法,因此我去父类中找。
在AbstractList中找到了
public boolean add(E e) {
add(size(), e);
return true;
}
接下来,我们在SubList中找到了实现方法
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
很明显,源代码和我们开始的分析是一致的,当然在添加之间需要进行空间容量判断,是否足以添加新的元素,扩容规则,我们上一章已经讲过。
三. 其他方法
关于SubList的其他方法,其实和add原理一样,不论是set(int index, E e),get(int index),addAll(Collection<? extends E> c),remove(int index),都是先判断当前传入的位置索引是否正确(如是否大于size,小于0等),再根据规则计算出原集合中的位置下标,最终完成对集合的操作。
四. 总结
本文续接上一章ArrayList原理及使用,对ArrayList中的常用方法subList进行了剖析,从源码的角度对通过subList方法得到的集合和原集合有何关系,有何不同点,从而避免工作中遇到各种坑,若有不对之处,请批评指正,望共同进步,谢谢!
再说Java集合,subList之于ArrayList的更多相关文章
- Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解Arra ...
- Java 集合:HashSet 与 ArrayList
Set 集合是无序不可以重复的的.List 集合是有序可以重复的. Java 集合:HashSet 与 hashCode.equals 博客里面已经说到这个问题,但是解释的还是不够清楚. 看一个小例子 ...
- java集合的实现细节--ArrayList和LinkedList
ArrayList和LinkedList的实现差异 List代表一种线性表的数据结构,ArrayList则是一种顺序存储的线性表,ArrayList底层采用动态数组的形式保存每一个集合元素,Link ...
- 【Java集合源代码剖析】ArrayList源代码剖析
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/mmc_maodun/article/details/35568011 转载请注明出处:http:// ...
- Java集合系列(二):ArrayList、LinkedList、Vector的使用方法及区别
本篇博客主要讲解List接口的三个实现类ArrayList.LinkedList.Vector的使用方法以及三者之间的区别. 1. ArrayList使用 ArrayList是List接口最常用的实现 ...
- Java集合源码剖析——ArrayList源码剖析
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- 【Java集合】试读ArrayList源码
ArrayList简介 ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAccess, ...
- Java 集合系列03之 ArrayList详细介绍
ArrayList做为List接口中最常用的实现类,必须掌握. 一.ArrayList简介 与Java中的数组相比ArrayList的容量可以动态增加.它继承与AbstractList,实现了List ...
- Java集合【8】-- ArrayList源码分析
目录 1. ArrayList 1.1 ArrayList特点介绍 1.2 实现的接口和继承的类 2. 成员变量 3. 构造方法 4. 常用增删改查方法 添加元素 查询元素 更新元素 删除元素 5.自 ...
- 【转】Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
原文网址:http://www.cnblogs.com/skywang12345/p/3308556.html 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具 ...
随机推荐
- Hibernate综合问题
n + 1问题 query.iterate()信息返回迭代查询将开始发表声明:录ID语句 Hibernate: select student0_.id ascol_0_0_from t_student ...
- Dual Dijkstra search for planning multiple paths
The dual Dijkstra search for planning multiple paths is performed by: (1) calculating a first shorte ...
- Codeforces 385 D Bear and Floodlight
主题链接~~> 做题情绪:时候最后有点蛋疼了,处理点的坐标处理晕了.so~比赛完清醒了一下就AC了. 解题思路: 状态压缩DP ,仅仅有 20 个点.假设安排灯的时候仅仅有顺序不同的问题.全然能 ...
- OpenGl中使用着色器的基本步骤及GLSL渲染简单示例
OpenGL着色语言(OpenGL Shading Language,GLSL)是用来在OpenGL中着色编程的语言,是一种具有C/C++风格的高级过程语言,同样也以main函数开始,只不过执行过程是 ...
- C Shell中的变量数组
今天刚刚在看一点C Shell的内容,发现一个挺好玩的东西!就是环境变量可以像数组那样来设置!具体设置语法如下: set variable=(element1 element2 ...) //注意元素 ...
- cocos2d-x 源代码分析 总文件夹
这篇博客用来整理与cocos2d-x相关的工作,仅仅要有新的分析.扩展或者改动,都会更改此文章. 祝大家愉快~ 1.源代码分析 1.CCScrollView源代码分析 http://blog.csdn ...
- python 教程 第十七章、 网络编程
第十七章. 网络编程 1) FTP客户端 import ftplib import os import socket HOST = '127.0.0.1' DIRN = 'menus' FILE ...
- 如何将任意文件固定在 Win10 的开始屏幕中
虽然Wox和Launchy是我日常启动程序的主力方式,不过开始屏幕的图标方便归类,这是快速启动工具所不能提供的,因此我也会将最常用的程序在开始屏幕上分类固定. 最近需要将一个常用的批处理文件(*.ba ...
- 2014辛星在读CSS第八节 使用背景图片
这应该是系统CSS本教程的最后一节,为什么,由于本节.我觉得基础已经完成,接下来的就是无休止的战斗,战斗非常难用知识讲出来,通过积累,战斗经验.这些都不是说出来. 伟大,下来说一下用CSS加入背景图片 ...
- SQL like使用 模糊查询
模糊查询: 参考资料:http://www.w3school.com.cn/sql/sql_wildcards.asp 在搜索数据库中的数据时,您能够使用 SQL 通配符. SQL 通配符 Like ...