一、 ArrayList简介
- ArrayList是我们在工作中经常使用的java的集合类,它的特点是非线程安全,顺序存取速度快。
- ArrayList继承了AbstractList,实现了List。它是一个数组队列,相当于动态数组。提供了相关的添加、删除、修改和遍历等功能。
二、 源码分析
-
ArrayList包含了两个重要的属性:elementData和size。 elementData:表示当前这个ArrayList实例总共可以装多少个元素。 size:当前这个ArrayList实例已经装了多少个元素。 其中elementData的声明方式为:private transient Object[] elementData;这里有一个问题,我们可以发现ArrayList是一个泛型类,但是这里没有使用 E[] elementData 来声明泛型数组?
1.1 实验创建一个泛型数组
public class GenericTest<T> { private T[] objArr; private int index = 0; public GenericTest() { // Error: Cannot create a generic array of T objArr = new T[10]; } }
在使用new T[10]来实例化时,编译器提示了错误 也就是说java不支持泛型数组的创建?
在Sun的一篇文档中里面提到了一种情况:
List<String>[] lsa = new List<String>[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.
这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList<Integer>而不会出现ArrayStoreException,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。
什么是类型擦除?类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。
- 构造方法
ArrayList提供了三种方式的构造器
/**
* 构造一个包含指定collection的元素的列表,
* 这些元素按照该collection的迭代器返回它们的顺序排列的
**/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
/**
* 构造一个指定初始容量的空列表
**/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* 构造一个默认初始容量为10的空列表
**/
public ArrayList() {
this(10);
}
-
调整数组容量
/** * 当ArrayList容量不足以容纳全部元素时,ArrayList会自动扩张容量, * 新的容量 = 原始容量 + 原始容量 / 2 + 1。 **/ public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }
-
添加
ArrayList提供了五种添加元素的方法
/**
* 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该
* 位置上的元素。
**/
public E set(int index, E element) {
RangeCheck(index);
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
/**
* 将指定的元素添加到此列表的尾部。
**/
public boolean add(E e) {
ensureCapacity(size + 1);
elementData[size++] = e;
return true;
}
/**
* 将指定的元素插入此列表中的指定位置。
* 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有
* 后续元素(将其索引加1)。
**/
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
// 如果数组长度不足,将进行扩容。
ensureCapacity(size+1);
// 将 elementData中从Index位置开始、长度为size-index的元素,
// 拷贝到从下标为index+1位置开始的新的elementData数组中。
// 即将当前位于该位置的元素以及所有后续元素右移一个位置。
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
/**
* 按照指定collection的迭代器所返回的元素顺序,将该
* collection中的所有元素添加到此列表的尾部。
**/
public boolean addAll(Collectionextends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
// 如果数组长度不足,将进行扩容。
ensureCapacity(size + numNew);
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
/**
* 从指定的位置开始,将指定collection中的所有元素插入到此列表
* 中。
**/
public boolean addAll(int index, Collectionextends E> c) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: " + index + ", Size: " + size);
Object[] a = c.toArray();
int numNew = a.length;
// 如果数组长度不足,将进行扩容。
ensureCapacity(size + numNew);
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;
}
-
查找
查找指定元素
/**
* 返回此列表中指定位置上的元素。
**/
public E get(int index) {
RangeCheck(index);
return (E) elementData[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;
} /** * 返回指定元素的最后一个匹配项的索引 **/ 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; } ``` 6. 删除
ArrayList提供了两种删除方法
/**
* 移除此列表中指定位置上的元素。
**/
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
return oldValue;
}
/**
* 移除此列表中首次出现的指定元素(如果存在)。这是因为
* ArrayList中允许存放重复的元素。
**/
public boolean remove(Object o) {
// 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 类似remove(int index),移除列表中指定位置上的元素。
// 无返回值
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
// 类似remove(int index),移除列表中指定位置上的元素。
// 无返回值
fastRemove(index);
return true;
}
}
return false;
}
ArrayList还提供了移除所有元素的方法
```
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
} 7. 其他
/** * 修整此ArrayList实例的是列表的当前大小的容量。应用程序可以使用此操作, * 以尽量减少一个ArrayList实例的存储。 **/ public void trimToSize() { modCount++; int oldCapacity = elementData.length; if (size < oldCapacity) { elementData = Arrays.copyOf(elementData, size); } } ```
三、 遍历方式
我们可以通过3种方式来遍历ArrayList
//通过迭代器遍历
Integer value = null;
Iterator it = list.iterator;
while (it.hasNext) {
value = (Integer)it.next;
}
//随机访问,通过索引值去遍历。由于ArrayList实现了RandomAccess接口,所以它支持通过索引值去随机访问元素。
Integer value = null;
int size = list.size();
for (int i = 0; i < size; i++) {
value = (Integer)list.get(i);
}
//for循环遍历
Integer value = null;
for (Integer integ:list) {
value = integ;
}
四、 遇见的错误
- 动态的删除ArrayList中的一些元素
for(int i = 0 , len = list.size();i < len; i++){
if(list.get(i) == XXX){
list.remove(i);
}
}
这种方式会抛出异常
<!-- lang: java -->
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
at java.util.ArrayList.RangeCheck(Unknown Source)
at java.util.ArrayList.get(Unknown Source)
at ListDemo.main(ListDemo.java:20)
因为你删除了元素,但是未改变迭代的下标,这样当迭代到最后一个的时候就会抛异常。 常用的动态删除元素方法有两种:
/**
* 在删除的同时对长度也进行减少
**/
int len = list.size();
for(int i = 0; i < len; i++){
if(list.get(i) == XXX){
list.remove(i);
--len;
--i;
}
}
/**
* List接口内部实现了Iterator接口,提供开发者一个iterator()得
* 到当前list对象的一个iterator对象。
**/
Iterator<String> sListIterator = list.iterator();
while(sListIterator.hasNext()){
String e = sListIterator.next();
if(e.equals("你好")){
sListIterator.remove();
}
}