一点一点看JDK源码(四)java.util.ArrayList 中篇

时间:2021-09-21 03:17:21

一点一点看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中所有类的基类,没有显示继承也是被继承的)

       一点一点看JDK源码(四)java.util.ArrayList 中篇

      因此,即使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方法访问)

      一点一点看JDK源码(四)java.util.ArrayList 中篇

        常量包括容量(size,MAX。。),底层存储结构(Object数组),序列化版本,默认储存等。

      2.1.2.构造器和初始化(可调用构造器)

        一点一点看JDK源码(四)java.util.ArrayList 中篇

        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

        

        内部保护方法:

          一点点去找内部保护方法去看定义,比较麻烦,可以直接看结构。

          若该方法有红色方框标记,就是不对外公开调用的了。如下图:

        一点一点看JDK源码(四)java.util.ArrayList 中篇

          内部保护方法,之所以不对外公开,是因为外部调用的时候,因为考虑不周,或调用方式错误,

          或者其他原因吧,将导致有错误出现,本身可能也并非是一个完整的操作链,所以保护起来。

          如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中一共有四个内部类,都是要用成员方法来调用的。内部类如下图:

          一点一点看JDK源码(四)java.util.ArrayList 中篇

          分别是ArrayListSpliterator,Itr,ListItr,SubList。调用的方法分别如下:

          一点一点看JDK源码(四)java.util.ArrayList 中篇

          具体用法,下篇再研究吧我!!

3.其他关注点

    发现了一些奇葩关注点,不知道有用没,说下而已。

    内部类的类名展示是使用$做连接符的,mybatis中的mapper.xml要使用内部类来接收的话,该内部类必须是静态的。

    貌似VM在编译的时候是拆分编译的,但是命名不是,还是按照内部处理的,如下图:

        一点一点看JDK源码(四)java.util.ArrayList 中篇

    成员变量elementData前有关键字transient进行修饰,表示该变量不参与实例化,应该是作为缓存的意思了。

     

    fastRemove不仅本类可用,util包下其他的类也可以调用还。

    要使用Collection下的Stream(聚合)方法的话,必须要将ArrayList用Collection来做对象的类型来接收,然后才可以使用。

    ArrayList是线程不安全的,那么modCount真的那么有用么,就不理解了,不会只用在序列化上吧。也没见到有回滚方法。

    官方介绍中,ArrayList在List接口下,List接口下的实例定义为随机存取不支持,感觉应该写的出来吧,只是压根没写。随

    机存有影响,随机取还不容易咩?没谁会考虑自己继承ArrayList然后重新扩展吧,估计也有可能,我没见过而已。

    ArrayList中对Arrays工具类,和System类都有应用。

    retainAll调用了内部方法batchRemove,作为一个交集判断操作,使用了缓冲区elementData。

      如果要提供取交集操作该多好!!

以上!