Java之ArrayList源码分析(第一篇:创建对象)

(注意:本文基于JDK1.8)

前言

    ArrayList位于java.util包中,一个表示线性表的容器类,底层基于数组存储元素(持有元素),可根据持有元素数量对数组容量进行动态扩充,所以常被称为动态数组。

    ArrayList仅可在单线程使用,如果在多线程下使用ArrayList,则会产生数据相互覆盖,删除多个,访问无法使用的下标(数组越界)。

    在多线程环境下,需要控制同一时刻元素的添加与删除。如果需要线程安全的List,可以使用Collections.synchronizedList()将ArrayList转换为线程安全或者使用另一个线程安全的容器类CopyOnWriteArrayList……

ArrayList操作元素的时间复杂度

元素插入到尾部:O(1)

元素插入到中间:O(n)

删除尾部的元素:O(1)

删除中间的元素:O(n)

查找元素:O(n)

访问元素(通过下标):O(1)

    为什么ArrayList操作元素的时间复杂度这样的呢?源码中有答案!

创建ArrayList对象的三个构造方法

    3个对外提供的构造方法均可用于创建ArrayList对象,经常使用的是无参的构造方法,每个构造方法做了什么?我们一起学习一下!

ArrayList无参数的构造方法

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    用于创建ArrayList对象的构造方法,ArrayList对象持有一个数组对象elementData,它的类型是[java.lang.Object(每个数组对象为一个数组类产生的对象),常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA是ArrayList类持有的一个数组对象(static成员称为类持有),它的类型同样是[java.lang.Object(在ArrayList类加载的部分,会详细介绍DEFAULTCAPACITY_EMPTY_ELEMENTDATA),实例变量elementData指向1个数组对象,数组对象负责持有添加的元素

方法体执行过程

1、将一个已经创建好的无元素的数组对象赋值给elementData

将ArrayList类持有的常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA指向的数组对象赋值给ArrayList对象持有的elementData

思考:作者为何使用一个常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA指向的数组对象呢?

答案:作者通过数组对象判断是哪一种初始化方式,当elementData指向的是常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA指向的数组对象时,会执行此种初始化方式的业务逻辑,这里巧妙使用常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA指向的对象作为标志位使用(这个思想值得学习)

ArrayList一个参数的构造方法

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

创建ArrayList对象并同时指定数组容量的构造方法,传入int参数initialCapacity作为数组对象的初始化容量

看下方法体是如何实现的?

针对传入的局部变量initialCapacity进行容错处理,对3种情况分别作出不同的业务逻辑处理

1、传入值大于0

传入值initialCapacity将作为一个新创建的Object数组对象的容量,接着将新创建的数组对象赋值给ArrayList对象持有的elementData,此时底层数组对象完成初始化

2、传入值等于0

将ArrayList对象持有的elementData指向一个由常量EMPTY_ELEMENTDATA持有的数组对象

3、传入值小于0

将无法创建ArrayList对象,会抛出一个IllegalArgumentException异常对象,该异常对象显式提醒调用者,传入的数组容量值是无效值,如果程序中未处理,程序会结束(这就是异常)

ArrayList只接受传入一个Collection对象的构造方法

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

用于创建ArrayList对象的构造方法,传入的Collection对象表示另外一个集合,该构造方法会限制传入的Collection对象持有的元素类型:该Collection对象持有的元素必须为ArrayList定义的参数类型E或E的子类,当我们需要创建ArrayList时,同时添加多个元素时,会用这个构造方法

1、将Collection对象转换为数组对象

首先将传入的Collection对象转换为数组对象,通过Collection对象自身的toArray()方法完成这项工作,接着将数组对象赋值给ArrayList对象持有的elementData

2、容量检测

接下来对elementData指向的数组对象进行容量检测,分别对两种情况作出处理

第一:先从ArrayList对象持有的数组对象elementData中,取得数组的长度,并赋值给ArrayList对象持有的实例变量size,对size值进行判断,size不等于0说明传入的Collection对象中实际持有元素,接下来elementData数组对象的实际类型进行检查,因elementData的静态类型为Object[],它可以指向任意一个引用数据类型数组的数组对象,所以这里做了保护,通过调用getClass()方法取出elementData指向的实际数组类型,如果实际的数组类型不是[Ljava.lang.Object,就通过Arrays.copyOf方法(见下方)将其复制一份为[Ljava.lang.Object类型的数组对象,再赋值给ArrayList持有的elementData进行持有

第二:传入的Collection对象没有持有元素,此时会将ArrayList类预先创建好的数组对象EMPTY_ELEMENTDATA赋值给ArrayList对象的实例变量elementData持有

Arrays工具类中的静态方法copyOf()分析

三个参数,第一个参数表示源数组对象(任意的数组类型),第二个参数为一个int表示新的数组对象长度,第三个参数表示数组元素的类型,为一个Class对象

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

该方法为范型方法,参数类型T、参数类型U,分别由传入参数的类型代替,最终返回一个参数类型T的数组对象为结束,内部过程总分为三步

1、创建对应的数组对象

分析传入参数newType的Class类型,如果是Object[]类型,则创建一个指向新长度newLength的Object[]类型的数组对象,否则创建一个指定新长度netLength,指定类型newType的数组对象

2、将传入的源数组对象中的每一个元素拷贝到新创建的数组对象中,通过System.arraycopy方法,完成这项工作

3、返回新创建的数组对象

System类的arraycopy()方法分析

五个参数,接受一个源数组对象、一个int(代表源数组对象的起始下标)、一个目标数组对象、一个int(代表目标数组对象的起始下标)、一个int(代表从源数组对象到目标数组对象要拷贝的元素总数)

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

native层方法……哥的软肋,谁让虚拟机是C和C++实现的呢

ArrayList类加载到JVM内存时,初始化的类变量介绍(5个)

1、常量serialVersionUID用于序列化技术,判断是否为同一个类

    private static final long serialVersionUID = 8683452581122892189L;

2、常量EMPTY_ELEMENTDATA

private static final Object[] EMPTY_ELEMENTDATA = {}; 

ArrayList需要传入容量的构造方法以及需要传入Collection的构造方法中使用了此常量,当传入的数组容量为0或传入Collection持有0个元素时,会赋值给ArrayList对象持有的实例变量elementData

3、常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ArrayList默认无参的构造方法使用了此常量,赋值给了实例变量elementData

4、常量DEFAULT_CAPACITY

    private static final int DEFAULT_CAPACITY = 10;

DEFAULT_CAPACITY表示数组默认容量,它的值是10

5、常量MAX_ARRAY_SIZE

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

表示数组对象的最大容量

对象创建时初始化的实例变量介绍

1、实例变量size

    private int size;

size在ArrayList中有两个用途

第一:表示ArrayList对象已经存储的实际元素总数

第二:作为下一次添加元素时的下标

2、实例变量elementData

    transient Object[] elementData; // non-private to simplify nested class access

ArrayList对象持有的数组对象,实际负责存储元素对象的引用(常常被称为ArrayList的底层数组对象,它是一个Object数组对象)

总结

1、ArrayList接受一个Collection对象的构造方法,如果你传入一个null,会抛出NullPointerException

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.ArrayList.<init>(ArrayList.java:179)
    at com.xx.gui.FkTest.main(FkTest.java:9)

2、ArrayList接受一个Collection对象的构造方法,传入的Collection对象,会调用对方的toArray()方法,如果对方的toArray()方法返回的不是Object[]类型的数组对象,ArrayList的内部会通过Arrays的copyOf()方法,把数组中的元素都复制到一个新的Object[]类型的数组对象中,并最后交给elementData进行持有

3、ArrayList对象持有一个数组对象elementData,该数组对象作为底层容器对象,负责实际保存元素

4、ArrayList对象持有的底层数组对象的默认容量是10,所以我们通常说创建一个ArrayList时,它的默认容量为10

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值