一点一点看JDK源码(四)java.util.ArrayList 中篇
liuyuhang原创,未经允许禁止转载
本文举例使用的是JDK8的API
目录:一点一点看JDK源码(〇)
1.综述
在前篇中,对于java.util.ArrayList进行了一些源码注释,能坚持看完的估计都是神一般的存在。
不过看源码并需要一个艰苦的过程,枯燥是很正常的。
但是不是要一直都很枯燥,本文将对此类进行分类解析。
2.关注点
2.0.ArrayList是如何构成的?
在java中,一个类的构成并不是十分复杂,列举出来,一页纸应该是足够的,我尝试下。
2.0.1.类的定义
类的定义中,会声明是class,AbstractClass,或者interface。
该类是否含有该类没有显示定义的方法,取决于向上有多少个extends的父类的存在。
该类是否有必须实现的方法,取决于向上有多少个implments的接口的存在。
2.0.2.类的构造
无参构造器,带参构造器。
构造器的方法名是和类名同名的,并且没有返回值。
构造器不管是无参的,还是带参的,不管使用何种方式调用,
都说明该类已经被实例化了。
构造器内也可以写很多奇葩的代码的,当然也许是常用手段。
在构造器内写代码以实现自己想实现的功能,相当于一些容器的init,实际上就是初始化。
2.0.3.类的成员变量
类的成员变量,不管如何定义,都是为该类内使用该变量提供一定的便利性
类的成员变量,理论上就是该类内的全局变量,如果是public,或者default,protected,
都是一种对该全局变量的开放性。
成员变量,可以是static的(静态,类加载即加载),可以是final的(只允许实例化一次)
2.0.4.类的成员方法
类的成员方法,在不论其使用范围的情况下,都是一种方法,根据实例化使用的构造不同,
可调用的方法范围不同。
若使用父类构造器来接收子类实例(如:Object obj = new ArrayList()),
会发现obj可使用的方法就变得很少了(只有Object的方法允许使用了)。如下图:
(Object是java中所有类的基类,没有显示继承也是被继承的)
因此,即使List list = new ArrayList();
用起来,字数更少,写起来更方便,但是失去了一些功能。
当然,简单的使用List接口来操作ArrayList实例化对象也是能满足一定要求的,
也并非不可以使用。
2.0.5.类的内部类
ArrayList也有一些内部类,内部类使用成员方法进行实例化,返回的是其Implments接口的对象。
由于其接口的方法在内部类中被复写,所以直接调用接口的方法,实际上是调用其内部类的方法。
关于内部类,下文中有列举。
2.0.6.类的实例化
类的成员方法,随着实例化时接收的对象类型不同而不同,因为我们只能调用对象所在类提供
的方法,所以了解ArrayList实例化后的特性,就应该使用ArrayList类来接收实例化对象。
(上文已有,不再赘述)
2.1.ArrayList提供了什么?
提供了什么?我也并非十分清楚。在第一篇中,我认为Collection下都是容器,因此作为一个容器,
应该提供容器应该有的特性吧,比如:
容器存储结构(底层存储结构)
容积计算(定容和扩容)
增删改查方法
特性方法(取决于储存结构和要实现的特性)
实际上这个内容还是能够进行一些分类的。ArrayList提供了如下具体内容:
2.1.1.常量和成员变量(无法直接访问,允许调用public方法访问)
常量包括容量(size,MAX。。),底层存储结构(Object数组),序列化版本,默认储存等。
2.1.2.构造器和初始化(可调用构造器)
clinit是该类在VM装载的时候初始化用的,暂不深究。
它提供了三个构造器:
一个无参构造器ArrayList();
两个带参构造器ArrayList(int)和ArrayList(Collection< ? extends E>)
初学的时候总有一种迷惑的感觉,构造器无非就是实例化的,为什么要提供好几个构造器?
这三个构造器都应该在什么时候使用呢?
如果不确定你定义这个容器的时候,容量多大,容器内容是什么,那么应该使用无参构造器。
如果确定你定义这个构造器的容量,或者至少容量会有多少,可以使用ArrayList(int)构造。
因为在ArrayList底层是Object数组,数组的容量是确定的,因此每次增加内容都需要对数组
进行扩容,扩容过程中要用新数组接收拷贝后的旧数组,所以节约计算资源效率,在能确定
容量的情况下,最好使用定容构造器ArrayList(int)。
如果一个容器内将直接增加数据,那么该数据最好是来自集合,也就是说向上两层的接口
Collection之下的所有结构,都可以直接转化为ArrayList的,此时就应该选择使用
ArrayList(Collection< ? extends E>)构造器了。
ArrayList查询快,增删慢,这个是官方说法。快慢实际上是相对而言的,相对于谁呢?
一般说到数组的相对性,都指的是链表。
即,接收参数的时候,使用链表,查询和加工参数的时候,使用数组。
2.1.3.成员方法
ArrayList提供的成员方法很多,主要分为三类:
1.增删改查对容器直接操作,归为一类。
2.内部保护方法,内部处理数据中调用的方法,或只暴露给uitl包的方法,无法公开调用。
3.其余的,对于容器特性的操作,或数据转化的操作,归为一类。
增:add,addAll,分别对应添加单个元素和添加多个元素,其中有对于index的指定参数时,
就是针对指定index后插入实参对象。位指定的时候默认加在末尾。
add和addAll的时候有进行扩容判断,扩容倍数为1.5倍。(先扩容,增加后再去掉空元素)
删:remove,removeAll,removeIf,分别对应删除单个元素,删除多个元素,按条件匹配删除。
传入参数有指定的index(按指定index删除),双index(按指定index范围删除),
Object(尝试找到此元素并删除,返回操作标识),Collection(删除指定集合内容)
Predicate接口(作为filter来进行是否删除的过滤,功能类似于Compare接口)。
有些删除的方法是带有返回值的,为boolean,或被删除的内容,应当接收,作为是否成功,
或者操作可能需要回滚的判断。
改:set,改指定index的值为实参对象。
查:get,根据index获取元素。indexOf,lastIndexOf,分别正序或倒叙根据元素查index
内部保护方法:
一点点去找内部保护方法去看定义,比较麻烦,可以直接看结构。
若该方法有红色方框标记,就是不对外公开调用的了。如下图:
内部保护方法,之所以不对外公开,是因为外部调用的时候,因为考虑不周,或调用方式错误,
或者其他原因吧,将导致有错误出现,本身可能也并非是一个完整的操作链,所以保护起来。
如fastRemove(int)方法,util包下都可以调用,有和remove有区别在于,它省略掉了index校验
还有rangeCheck(int),是专门用于index校验的方法,也没有必要对外公开,它属于
其他方法,如add,addAll的一部分,这种方法抽取出来的原因,多数因为出现次数超过三次,
因此就有必要进行重新封装了。
特性操作:
特性操作细分下来,也可以按照功能进行细分。如:
清空(clear),克隆(clone),容量(size),判空(isEmpty),判断包含(cantains),
容量优化(ensureCapacity),遍历(forEach),迭代器(iterator等),拆分(subList),
拆分迭代(spliterator),比较排序(sort),转数组(toArray),替换(replace),
去空(trimToSize),求交集(retainAll)
其中,清空,替换,排序,容量优化,都是对ArrayList自身的操作。
克隆,容量,判断,都是对ArrayList的一种特性或内容查询方式。
而遍历,迭代,拆分,迭代拆分,就纯粹是数据加工,而获得其他对象了。
其中拆分,迭代拆分,Collection接口下的Stream(聚合)都是1.8新增的了。
2.1.4.成员方法调用的内部类
ArrayList中一共有四个内部类,都是要用成员方法来调用的。内部类如下图:
分别是ArrayListSpliterator,Itr,ListItr,SubList。调用的方法分别如下:
具体用法,下篇再研究吧我!!
3.其他关注点
发现了一些奇葩关注点,不知道有用没,说下而已。
内部类的类名展示是使用$做连接符的,mybatis中的mapper.xml要使用内部类来接收的话,该内部类必须是静态的。
貌似VM在编译的时候是拆分编译的,但是命名不是,还是按照内部处理的,如下图:
成员变量elementData前有关键字transient进行修饰,表示该变量不参与实例化,应该是作为缓存的意思了。
fastRemove不仅本类可用,util包下其他的类也可以调用还。
要使用Collection下的Stream(聚合)方法的话,必须要将ArrayList用Collection来做对象的类型来接收,然后才可以使用。
ArrayList是线程不安全的,那么modCount真的那么有用么,就不理解了,不会只用在序列化上吧。也没见到有回滚方法。
官方介绍中,ArrayList在List接口下,List接口下的实例定义为随机存取不支持,感觉应该写的出来吧,只是压根没写。随
机存有影响,随机取还不容易咩?没谁会考虑自己继承ArrayList然后重新扩展吧,估计也有可能,我没见过而已。
ArrayList中对Arrays工具类,和System类都有应用。
retainAll调用了内部方法batchRemove,作为一个交集判断操作,使用了缓冲区elementData。
如果要提供取交集操作该多好!!
以上!