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 ? 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); }
控制台:
收工