ArrayList主要用做動態數組,是增強版的Array,能夠在添加元素時無需擔憂容量不夠,也支持經過index獲得元素。java
ArrayList定義:數組
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
能夠得出ArrayList支持泛型。app
先來分析ArrayList的父類和接口:dom
1. AbstractList:提供List接口的默認實現,個別爲抽象方法;ide
2.List定義List(即列表)必須實現的方法,咱們一般定義一個ArrayList是這樣:List list = new ArrayList(),而非這樣ArrayList list = new ArrayList(),也正是因爲這點,這樣還能夠實現多態,來下降耦合度;測試
3.RandomAccess:這是一個標記接口,具體使用請參照RandomAccess接口的使用,不過值得關注的是這段話:ui
JDK中推薦的是對List集合儘可能要實現RandomAccess接口 若是集合類是RandomAccess的實現,則儘可能用for(int i = 0; i < size; i++) 來遍歷而不要用Iterator迭代器來遍歷,在效率上要差一些。反過來,若是List是Sequence List,則最好用迭代器來進行迭代。
4.Cloneable:這也是一個標記接口,表示實現該接口的類,能夠經過Object的clone方法獲得一個該對象的淺拷貝(僅僅拷貝該對象中的基本類型與該對象,而該對象中全部的對其餘對象的引用仍然指向原來的對象,沒有拷貝),例子:this
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引用的同一個對象)spa
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.net
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 ? get(i)==null : 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 ? get(i)==null : 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 ? get(i)==null : 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); }
控制檯:
收工