常用集合源码解析(一),ArrayList

ArrayList主要用作动态数组,是加强版的Array,可以在添加元素时无需担心容量不够,也支持通过index得到元素。

ArrayList定义

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

可以得出ArrayList支持泛型。

先来分析ArrayList的父类和接口:

1. AbstractList:提供List接口的默认实现,个别为抽象方法;

2.List定义List(即列表)必须实现的方法,我们通常定义一个ArrayList是这样:List list = new ArrayList(),而非这样ArrayList list = new ArrayList(),也正是由于这点,这样还可以实现多态,来降低耦合度;

3.RandomAccess:这是一个标记接口,具体使用请参照RandomAccess接口的使用,不过值得关注的是这段话:

JDK中推荐的是对List集合尽量要实现RandomAccess接口
如果集合类是RandomAccess的实现,则尽量用for(int i = 0; i < size; i++) 来遍历而不要用Iterator迭代器来遍历,在效率上要差一些。反过来,如果List是Sequence List,则最好用迭代器来进行迭代。

4.Cloneable:这也是一个标记接口,表示实现该接口的类,可以通过Object的clone方法得到一个该对象的浅拷贝(仅仅拷贝该对象中的基本类型与该对象,而该对象中所有的对其他对象的引用仍然指向原来的对象,没有拷贝),例子:

public class Person implements Cloneable {

    
    String name;
    int age;
    User user;
    
    
    
    public Person(){}
    
    public Person(String name,int age,User user){
        this.name = name;
        this.age = age;    
        this.user = user;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String toString()
    {
        return "this is person:"+"name:"+this.name+"——age:"+this.age;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    
}

public class User {

    
    String name;
    String password;
    int age;
    
    public User() {
        
    }

    public User(String name,String password,int age)
    {
        this.name = name;
        this.password = password;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String toString()
    {
        return "this is user:"+"name:"+this.name+"——password:"+this.password+"——age:"+this.age;
    }
}

public class CloneTest {
    
    
    public static void main(String[] args) throws Exception {
        User user = new User("user","kk",20);
        Person a = new Person("person",20, user);
        Person b = (Person) a.clone();
        
        System.out.println("Person b:" + b.name + " Person b:user:" + b.user.name);
        System.out.println("Person a:" + a.name + " Person a:user:" + a.user.name);
        
        b.name = "personnamechange";
        b.user.name = "usernamechange";
        
        System.out.println("Person b:" + b.name + " Person b:user:" + b.user.name);
        System.out.println("Person a:" + a.name + " Person a:user:" + a.user.name);
    }

}

输出为:(可以看到将b中的user的name修改后a中user的name也会改变,可得两者中的user引用的同一个对象)

Person b:person Person b:user:user
Person a:person Person a:user:user
Person b:personnamechange Person b:user:usernamechange
Person a:person Person a:user:usernamechange

5.Serializable:标记接口,实现该接口表示该类的对象可进行序列化。Java的对象序列化是指将那些实现了Serializable接口的对象转换成一个字符序列,并能够在以后将这个字节序列完全恢复为原来的对象。例子:

import java.io.Serializable;

public class User implements Serializable {

    
    private String name;
    private String password;
    private int age;
    
    public User() {
        
    }

    public User(String name,String password,int age)
    {
        this.name = name;
        this.password = password;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String toString()
    {
        return "this is user:"+"name:"+this.name+"——password:"+this.password+"——age:"+this.age;
    }
}

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerialTest {

public static void main(String[] args) {
        
        User p1 = (User) deSerialByte(serialByte(new User("user","1234",15)));
        
        
        System.out.println("p1:"+p1.toString());
        
    }
    
    //序列化一个对象(可以存储到一个文件也可以存储到字节数组)这里存储到字节数组
    public static byte[] serialByte(Object obj)
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
            oos.close();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    //反序列化一个对象
    public static Object deSerialByte(byte[] by)
    {
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(by));
            return ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}

若User未实现Serializable接口,那么writeObject时将会报NotSerializableException

成员变量:

private static final long serialVersionUID = 8683452581122892189L;

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */
    private transient Object[] elementData;

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

1.serialVersionUID:序列化ID,用于序列化

2.elementData:对象数组,存储ArrayList中的元素,至于transient表示该成员变量在序列化时将不被纳入序列化范围内,那么这个数组该如何被序列化呢,可以看到ArrayList自身实现了对elementData的序列化与反序列化

/**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out array length
        s.writeInt(elementData.length);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

    }

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in array length and allocate array
        int arrayLength = s.readInt();
        Object[] a = elementData = new Object[arrayLength];

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++)
            a[i] = s.readObject();
    }

3.size:ArrayList包含元素的数量

构造方法:

/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this(10);
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

该三个构造方法第一个可通过参数指定elementData的容量大小,第二个无参方法可以看到若未指定容量那么elementData初始大小为10,第三个可将其他集合转成数组返回给elementData(返回若不是Object[]将调用Arrays.copyOf方法将其转为Object[])。

成员方法:列举常用

add方法

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

第一个add方法:直接在尾部添加元素,并在添加前确保容量足够(ensureCapacityInternal),若不够重新指定容量后,使用Arrays.copyOf重新创建数组,并将原来的elementData拷贝进去。

第二个add方法:可指定下标进行元素添加,首先判断指定位置index是否超出elementData的界限,再确保容量足够,并使用System.arraycopy将index后的元素整体后移一位,将index位置空出,之后插入元素。

这里有一个变量modCount需要注意,

The number of times this list has been structurally modified.

    这是对modCount的解释,意为记录list结构被改变的次数(观察源码可以发现每次调用ensureCapacoty方法,modCount的值都将增加,但未必数组结构会改变,所以感觉对modCount的解释不是很到位)。

addAll方法

/**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the
     * specified collection's Iterator.  The behavior of this operation is
     * undefined if the specified collection is modified while the operation
     * is in progress.  (This implies that the behavior of this call is
     * undefined if the specified collection is this list, and this
     * list is nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

    /**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element from the
     *              specified collection
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

第一个方法:将一个集合转成数组,确保容量足够,添加到ArrayList尾部,只要集合c的大小不为空,即转换后的数组长度不为0则返回true,此处有一疑问,即我们在构造方法中知道在使用toArray方法还需对返回值实际类型是否是Object数组进行判断,若不是则要将实际类型转为Object数组,而在addAll方法却没有此操作,为什么?

第二个方法:将一个集合转成数组,确保容量足够,与add类似通过System.arraycopy将index后的元素后移c.size位,确保这个集合中元素能插入当中。

clear方法:

 

/**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

 

将所有元素置成null,并未对数组容量进行释放。

indexOf、lastIndexOf方法:

/**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    /**
     * Returns the index of the last occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the highest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

可以看出这两个方法都是遍历elementData数组,一个正向遍历一个反向遍历,indexOf返回正向查找到的第一个元素的下标,lastIndexOf返回反向查找到的第一个元素的下标。

get、set方法:

/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

/**
     * Checks if the given index is in range.  If not, throws an appropriate
     * runtime exception.  This method does *not* check if the index is
     * negative: It is always used immediately prior to an array access,
     * which throws an ArrayIndexOutOfBoundsException if index is negative.
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

get方法先检查查找的index是否越界,再直接通过下标返回元素;

set方法同样先检查index是否越界,在直接通过赋值将之前的元素替换成指定元素,并返回之前元素。

isEmpty方法

无脑判断size是否为0,并返回。

remove方法

/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

第一个根据下标进行元素删除,首先检查index是否越界,再将该元素取出,并将elementData中在index后的元素整体前移一位,返回该元素;

第二个是根据元素进行删除,先正向遍历elementData数组,若有相同元素,得到该元素下标,进行如第一个方法的删除操作,返回true,若未遍历到该元素则返回false,值得注意的是,这个remove只删除正向遍历找到的第一个相同元素,而不是删除所有与参数相同的元素。

trimToSize方法

/**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

这个方法用来释放elementData多余的容量,使用Arrays.copyOf将elementData容量重置为size,正好可以存下当前ArrayList中的元素。

clone方法

 /**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

该方法返回此 ArrayList 实例的浅表副本。(不复制这些元素本身,而是直接引用这些元素),由此也可以得出Array.copyOf方法返回的数组中元素是原数组元素的引用。

 

疑问解决:在addAll方法那里,我留下了一个疑问,在此给出解答,首先解答为何在构造方法中进行类型判断并强转,注释上写了这么一句话

// c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);

c.toArray方法可能不是返回的Object方法,于是我进行了一个测试:

@Test
    public void test(){
        List<String> strList = Arrays.asList("test", "hello");
        Object[] a = strList.toArray();
        System.out.println(a.getClass());
    }

控制台打出:

那么为何要进行实际类型转换呢?在Java中,允许子类向上转型(Object是所有类的父类),所以可以这样使用:Object[] a = strList.toArray();

但是如果进行如下操作:

@Test
    public void test(){
        List<String> strList = Arrays.asList("test", "hello");
        Object[] a = strList.toArray();
        
        a[0] = new Object();
        System.out.println(a[0]);
    }

会抛出异常:

java.lang.ArrayStoreException: java.lang.Object;

这也就是说假如我们有1个Object[]数组,并不代表着我们可以将Object对象存进去,这取决于数组中元素实际的类型,那么为了将Object存进去,就要对a进行实际类型转换,如下

@Test
    public void test(){
        List<String> strList = Arrays.asList("test", "hello");
        Object[] a = strList.toArray();
        System.out.println(a.getClass());
        a = Arrays.copyOf(a, strList.size(), Object[].class);
        
        a[0] = new Object();
        System.out.println(a.getClass());
    }

控制台打出:

强转之后将Object对象存入便不会报错了。因为a的实际类型已是Object[],此时将任意类型的对象存入该数组都不再报错,如此转成Object[]就达到了ArrayList的目的,在未指定具体元素类型时,任何类型都可以存入其中:例子:

@Test
    public void test(){
        List<String> strList = Arrays.asList("test", "hello");
        Object[] a = strList.toArray();
        System.out.println(a.getClass());
        a = Arrays.copyOf(a, strList.size(), Object[].class);
        
        a[0] = new Person();
        a[1] = new User();
        System.out.println(a[0].getClass() + " " + a[1].getClass());
    }

控制台打出:

那么为何addAll没有对c进行Object[]强转呢?ArrayList可没有保证这个c也要像ArrayList一样,在未指定元素具体类型时什么类型都能存,ArrayList只需保证自己有这个厉害的特质就行了,其他的就原封不动的装进去就好

ArrayList的三个构造方法都有elementData = new Object[]这一操作,他们已经让elementData引用了一个Object[](已是Object[]),所以之后在add操作上,由于允许子类向上转型,于是所有的类型便都可以存入其中了。

 测试一下:

@Test
    public void test2(){
        List list = new ArrayList();
        list.add("hello");
        list.add(new User());
        List<Integer> objList = Arrays.asList(1,2,3);
        list.addAll(objList);
        System.out.println(list);
    }

控制台:

收工

posted on 2017-10-10 14:27  T~Z  阅读(265)  评论(0编辑  收藏  举报