Java 集合框架系列
Java 集合框架_開篇
Java 集合框架_List
Java 集合框架_ArrayList
Java 集合框架_LinkedList
上一章講述了頂層接口Collection以及抽樣類AbstractCollection。但是Collection接口集合有個缺陷,它只提供遍歷集合,添加,刪除的方法,沒有提供讓我們快速找到集合中某個元素的方法。這個就要使用List集合了。
一. List 接口
public interface List<E> extends Collection<E> {}
List接口繼承自Collection接口,它提供一種索引概念,就像數組下標一樣,讓我們可以快速找到對應索引位置的元素,也可以在索引位置添加,刪除,替換對應元素。
對比Collection接口,想一下List接口擁有的方法,肯定與索引有關系。
- 添加元素:
// 繼承自Collection接口方法
boolean add(E e);
boolean addAll(Collection<? extends E> c);
// List接口新加方法,在指定索引位置添加元素
void add(int index, E element);
boolean addAll(int index, Collection<? extends E> c);
- 刪除元素
// 繼承自Collection接口方法
boolean remove(Object o);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
// List接口新加方法,刪除指定索引位置元素
E remove(int index);
- 替換元素
// List接口新加方法, 替換指定索引位置的元素
E set(int index, E element);
- 查詢操作
// 繼承自Collection接口方法
boolean contains(Object o);
boolean containsAll(Collection<?> c);
Iterator<E> iterator();
// List接口新加方法, 根據索引查找對應元素,根據元素查找索引位置
E get(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
// List接口新加方法, 返回功能性更強的迭代器ListIterator
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
- 其他重要方法
int size();
boolean isEmpty();
Object[] toArray();
<T> T[] toArray(T[] a);
// List接口新加方法,返回fromIndex位置到toIndex位置的子集合,
// (包括fromIndex,不包括toIndex)
List<E> subList(int fromIndex, int toIndex);
- 整體預覽
public interface List<E> extends Collection<E> {
// 添加元素
// 繼承自Collection接口方法
boolean add(E e);
boolean addAll(Collection<? extends E> c);
// List接口新加方法,在指定索引位置添加元素
void add(int index, E element);
boolean addAll(int index, Collection<? extends E> c);
// 刪除元素
// 繼承自Collection接口方法
boolean remove(Object o);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
// List接口新加方法,刪除指定索引位置元素
E remove(int index);
// 更新元素
// List接口新加方法, 替換指定索引位置的元素
E set(int index, E element);
// 查詢方法
// 繼承自Collection接口方法
boolean contains(Object o);
boolean containsAll(Collection<?> c);
Iterator<E> iterator();
// List接口新加方法, 根據索引查找對應元素,根據元素查找索引位置
E get(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
// List接口新加方法, 返回功能性更強的迭代器ListIterator
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
int size();
boolean isEmpty();
Object[] toArray();
<T> T[] toArray(T[] a);
// List接口新加方法,返回fromIndex位置到toIndex位置的子集合,(包括fromIndex,不包括toIndex)
List<E> subList(int fromIndex, int toIndex);
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
boolean equals(Object o);
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED);
}
}
二. ListIterator接口
在List接口中,我們發現它返回了一個新的迭代器類型ListIterator。這個又是做什么用的呢?
我們知道Iterator接口有三個方法(forEachRemaining請忽略不計)。通過hasNext和next方法來遍歷數組,通過remove方法來刪除數組。
你會發現它沒有添加和替換的方法,那是因為不同集合根據它數據結構的不同,添加和替換的操作不好界定,所以就交給它們各自獨特的迭代器來實現,比如說這里的ListIterator。而最頂層的迭代器只提供這個三個方法。
我們知道List集合是可以通過索引查找集合中的元素,所以List集合一個連續的集合,可以查找前一個索引的元素,也可以查找后一個索引的元素,還可以添加替換元素。
public interface ListIterator<E> extends Iterator<E> {
// 繼承自Iterator中的方法
boolean hasNext();
E next();
void remove();
// 反向遍歷集合時使用
boolean hasPrevious();
E previous();
// 如果是正向遍歷集合,nextIndex返回值表示集合中下一個元素的索引位置。
// 如果是反向遍歷集合,nextIndex返回值表示集合中當前元素的索引位置。
int nextIndex();
// 如果是正向遍歷集合,previousIndex返回值表示集合中當前元素的索引位置。
// 如果是反向遍歷集合,previousIndex返回值表示集合中前一個元素的索引位置。
int previousIndex();
// 用元素e替換當前索引位置的元素
void set(E e);
// 在當前索引下一個位置添加元素e,再將索引位置加1,不遍歷新添加的元素。
// 保證我們完整地遍歷集合中原有的元素,而使用迭代器的刪除,替換,添加操作,都不會影響本次遍歷過程。
void add(E e);
}
這里有一點需要注意,當我們得到一個集合迭代器,進行遍歷的時候,我們有可能在遍歷過程中,用迭代器進行刪除,添加,替換操作。要保證一點,就是這些操作不影響當前迭代過程,也就是說遍歷得到的還是原來集合的數據。
三. AbstractList 抽樣類
AbstractList有一個非常重要的成員屬性modCount。
它用在多線程環境下,某個正在遍歷的集合,是否被別的線程修改了,如果是,那么就會拋出ConcurrentModificationException異常。也就是說這個異常是在迭代器中拋出的。
3.1 添加元素
public boolean add(E e) {
add(size(), e);
return true;
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
add(E e) 方法是向集合末尾添加元素。而AbstractList 默認是不可修改的集合,需要自己手動復寫add(int index, E element)方法,才能添加元素。
boolean addAll(Collection<? extends E> c)這個方法是在AbstractCollection中實現的。
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
boolean modified = false;
for (E e : c) {
add(index++, e);
modified = true;
}
return modified;
}
private void rangeCheckForAdd(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
rangeCheckForAdd方法是檢查索引是否越界的。然后遍歷c集合,將每個元素添加到本集合中。
3.2 刪除元素
刪除元素的方法,大部分使用AbstractCollection類提供的實現。
public E remove(int index) {
throw new UnsupportedOperationException();
}
AbstractList是個不可修改的集合,所以這里做了限制。
3.3 更新元素
public E set(int index, E element) {
throw new UnsupportedOperationException();
}
理由同上。
3.4 查詢方法
boolean contains(Object o)和 boolean containsAll(Collection<?> c)方法采用AbstractCollection類提供的實現。
public Iterator<E> iterator() {
return new Itr();
}
public ListIterator<E> listIterator() {
return listIterator(0);
}
public ListIterator<E> listIterator(final int index) {
rangeCheckForAdd(index);
return new ListItr(index);
}
AbstractList提供了兩個迭代器子類 Itr 和 ListItr ,之后我們將來分析它。
abstract public E get(int index);
get(int index)方法強制子類必須實現。
public int indexOf(Object o) {
ListIterator<E> it = listIterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return it.previousIndex();
} else {
while (it.hasNext())
if (o.equals(it.next()))
return it.previousIndex();
}
return -1;
}
正向查找某個元素在集合中的位置,如果沒找到就返回-1.這里就要用到ListIterator迭代器了,因為它可以集合中的位置索引。
注意當我們正向遍歷集合的時候,previousIndex()方法返回的就是當前元素的位置,而并不是前一個元素的位置。這個時候nextIndex()方法返回的是下一個元素的位置。
public int lastIndexOf(Object o) {
ListIterator<E> it = listIterator(size());
if (o==null) {
while (it.hasPrevious())
if (it.previous()==null)
return it.nextIndex();
} else {
while (it.hasPrevious())
if (o.equals(it.previous()))
return it.nextIndex();
}
return -1;
}
反向查找某個元素在集合中的位置,如果沒找到就返回-1。
注意當我們反向遍歷集合的時候,nextIndex()方法返回的就是當前元素的位置,而并不是下一個元素的位置。這個時候previousIndex()方法返回的是前一個元素的位置。
3.5 其他重要方法
int size()、boolean isEmpty()、Object[] toArray()、<T> T[] toArray(T[] a)方法都采用AbstractCollection類提供的實現。
public List<E> subList(int fromIndex, int toIndex) {
return (this instanceof RandomAccess ?
new RandomAccessSubList<>(this, fromIndex, toIndex) :
new SubList<>(this, fromIndex, toIndex));
}
AbstractList提供默認subList方法的實現。之后我們再來分析SubList和RandomAccessSubList這兩個類。
四. AbstractList的內部類 Itr
迭代器的實現一般都是某個集合的內部類,因為這樣就可以直接訪問集合的成員屬性,修改操作集合中的元素。通過Itr我們來了解怎樣簡單地實現一個迭代器。
4.1 成員屬性
// 光標索引位置,0表示在集合開始位置(因為這是一個list集合,所以可以用索引表示開始位置)
int cursor = 0;
// 表示讀取到的當前元素位置
int lastRet = -1;
// 用來判斷原集合是否被修改,拋出ConcurrentModificationException異常。
int expectedModCount = modCount;
4.2 遍歷集合
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
如果集合中modCount的值與迭代器中expectedModCount的值不相等,就說明在迭代器期間集合被修改了,那么遍歷的數據已經失效,就拋出異常。
public boolean hasNext() {
return cursor != size();
}
判斷集合中是否還有未讀取的元素,即cursor光標已經移動到最后了。
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
- 先檢查集合有沒有沒被修改。
- 然后調用list集合的get(i)方法,獲取cursor光標位置的元素。
- 因為我們獲取了元素,就要將元素的索引cursor賦值給lastRet變量。
- 將cursor光標位置加1,指向下一個元素。
- 最后返回獲取的元素。
4.3 刪除集合中元素
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
- 如果lastRet < 0表示當前位置元素無效,不能進行刪除操作,拋出異常。
- 調用List集合remove(lastRet)方法刪除集合當前元素。
- 如果lastRet < cursor為真,就讓cursor光標自減1。
刪除一個元素,cursor光標值的變化分兩種情況。正向遍歷,cursor光標指向下一個元素,這時集合刪除一個元素,原集合下一個元素移動到當前位置,所以cursor光標要減一。反向遍歷,因為是從后向前遍歷,那么刪除一個元素,對于坐標位置沒有任何影響。通過lastRet < cursor來判斷是正向遍歷還是反向遍歷。
- 將lastRet重置為-1,因為當前元素位置的元素已經被移除了,不能在刪除了。
- expectedModCount = modCount 這個很重要,因為我們調用了集合的remove方法修改了集合,所以集合的modCount值就改變了,要重新賦值,防止拋出ConcurrentModificationException異常。
五. AbstractList的內部類 ListItr
它繼承自Itr類,實現了ListIterator接口。它實現了對List集合的反向遍歷,以及添加和替換集合中元素的方法。
5.1 構造函數
ListItr(int index) {
cursor = index;
}
表示從index - 1位置開始,反向遍歷集合元素。
5.2 反向遍歷集合
public boolean hasPrevious() {
return cursor != 0;
}
判斷集合中是否還有未讀取的元素。當cursor==0,表示已經讀取到第一元素了,前面以及沒有元素了。
public E previous() {
checkForComodification();
try {
int i = cursor - 1;
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
- 先檢查集合有沒有沒被修改。
- cursor - 1表示前一個元素的索引。
- 調用list集合的get(i)方法,獲取對應索引位置的元素。
- lastRet = cursor = i; 表示cursor光標就表示當前元素的索引位置
- 最后返回獲取的元素。
5.3 返回索引位置。
// 如果是正向遍歷集合,nextIndex返回值表示集合中下一個元素的索引位置。
// 如果是反向遍歷集合,nextIndex返回值表示集合中當前元素的索引位置。
public int nextIndex() {
return cursor;
}
// 如果是正向遍歷集合,previousIndex返回值表示集合中當前元素的索引位置。
// 如果是反向遍歷集合,previousIndex返回值表示集合中前一個元素的索引位置。
public int previousIndex() {
return cursor-1;
}
5.4 替換當前元素
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
- lastRet < 0 表示當前位置無效,不能更換元素,拋出異常。
- checkForComodification方法檢查集合有沒有沒被修改。
- 調用List集合的set(lastRet, e)方法,替換當前元素。
- 因為修改了集合,那么重新賦值expectedModCount = modCount
5.5 添加元素
public void add(E e) {
checkForComodification();
try {
int i = cursor;
AbstractList.this.add(i, e);
lastRet = -1;
cursor = i + 1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
- 調用checkForComodification方法檢查集合有沒有沒被修改。
- 調用List的add(i, e)方法,在cursor光標位置插入元素。
那么就有兩種情況了,正向遍歷的時候,就是在當前元素下一個索引位置插入,而反向遍歷時,就是在當前元素索引位置插入。
- lastRet = -1 設置當前元素索引位置無效。
- cursor = i + 1 將光標位置加1
這里就有問題了,它只能保證正向遍歷的時候,不會遍歷到剛剛插入的元素。但是反向遍歷的時候,因為將cursor光標位置加一,那么下次獲取前一個元素正好是剛剛添加的元素。
- 因為修改了集合,那么重新賦值expectedModCount = modCount
小結
我們看到迭代器一般作為集合的內部類,調用集合中的方法,來操作集合中的元素的。它還有個重要方法checkForComodification,來判斷在迭代過程中,是否有其他線程修改了原集合。
六. SubList 子集合類
它是List集合的一部分子集,通過List集合的subList(int fromIndex, int toIndex)方法獲取(包括fromIndex但是不包括toIndex),對它的操作其實都是調用父集合對應方法,本質上子集合就是父集合一段區間的投影,而并不是獨立的。
6.1 成員屬性
// 父集合的引用
private final AbstractList<E> l;
// 偏移量
private final int offset;
// 子集合的大小
private int size;
6.2 構造函數
SubList(AbstractList<E> list, int fromIndex, int toIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > list.size())
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
l = list;
offset = fromIndex;
// 所以子集合不包含toIndex索引位置的元素,不然這里要加1
size = toIndex - fromIndex;
this.modCount = l.modCount;
}
對成員屬性進行初始化賦值。注意子集合SubList的modCount和父集合的modCount值一樣。
6.3 檢測的方法
// 檢查父集合有沒有被修改
private void checkForComodification() {
if (this.modCount != l.modCount)
throw new ConcurrentModificationException();
}
// 獲取元素的時候,要檢查索引位置,要在[0, size)區間內
private void rangeCheck(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 添加元素的時候,要檢查索引位置,要在[0, size]區間內,包括size位置,表示在集合末尾添加元素
private void rangeCheckForAdd(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
6.4 操作集合
子集合操作元素的方法都是調用父集合對應方法,因為它本身就是父集合的一個投影。下面以add(int index, E element)方法為例
public void add(int index, E element) {
rangeCheckForAdd(index);
checkForComodification();
l.add(index+offset, element);
this.modCount = l.modCount;
size++;
}
- rangeCheckForAdd(index) 檢查索引是否越界
- checkForComodification() 檢查父集合有沒有沒被修改
- l.add(index+offset, element) 調用父集合對應方法,添加元素,注意索引要加上偏移量,就是在父集合對應索引的值。
- this.modCount = l.modCount 更改子集合modCount的值。
- 因為添加了一個元素,所以size要自增。
6.5 迭代器
因為子集合是父集合區間的投影,那么采用父集合的迭代器就很方便實現子集合的迭代器了。
public ListIterator<E> listIterator(final int index) {
// 檢查父集合有沒有被修改
checkForComodification();
// 檢查索引是否越界
rangeCheckForAdd(index);
return new ListIterator<E>() {
// 得到父集合的迭代器,將開始位置設在index+offset
private final ListIterator<E> i = l.listIterator(index+offset);
// nextIndex方法返回是子集合索引位置。 使用i.nextIndex() - offset得到
public boolean hasNext() {
return nextIndex() < size;
}
// 調用父集合迭代器對應方法
public E next() {
if (hasNext())
return i.next();
else
throw new NoSuchElementException();
}
public boolean hasPrevious() {
return previousIndex() >= 0;
}
// 調用父集合迭代器對應方法
public E previous() {
if (hasPrevious())
return i.previous();
else
throw new NoSuchElementException();
}
// 減去偏移量,得到在子集合真實索引位置
public int nextIndex() {
return i.nextIndex() - offset;
}
// 減去偏移量,得到在子集合真實索引位置
public int previousIndex() {
return i.previousIndex() - offset;
}
// 調用父集合迭代器對應方法,并重設modCount和size的值
public void remove() {
i.remove();
SubList.this.modCount = l.modCount;
size--;
}
// 調用父集合迭代器對應方法
public void set(E e) {
i.set(e);
}
// 調用父集合迭代器對應方法,并重設modCount和size的值
public void add(E e) {
i.add(e);
SubList.this.modCount = l.modCount;
size++;
}
};
}
可以看出來,都是調用父集合迭代器對應方法,但是要注意一下,子集合在父集合的偏移量。
七. RandomAccessSubList 子集合類
它繼承SubList類,并實現RandomAccess這個可隨機訪問的標記接口。
class RandomAccessSubList<E> extends SubList<E> implements RandomAccess {
RandomAccessSubList(AbstractList<E> list, int fromIndex, int toIndex) {
super(list, fromIndex, toIndex);
}
public List<E> subList(int fromIndex, int toIndex) {
return new RandomAccessSubList<>(this, fromIndex, toIndex);
}
}
總結
AbstractList抽樣類實現了List接口的一些工具性的方法,它內部還實現了迭代器內部類。繼承AbstractList抽樣類值需要復寫get(int index)和size()方法。
List 接口
List接口繼承自Collection接口,它提供了一種索引概念,我們可以通過索引來查找,修改,或刪除元素。它新添加的方法:
- void add(int index, E element); 在指定索引位置刪除元素
- boolean addAll(int index, Collection<? extends E> c); 在指定索引位置 添加一個集合中全部元素
- E remove(int index); 移除指定索引位置元素
- E set(int index, E element); 替換指定索引位置的元素
- E get(int index); 獲取指定索引處元素
- int indexOf(Object o); 查找o對象在集合中的索引位置(正向查找)
- int lastIndexOf(Object o); 查找o對象在集合中的索引位置(反向查找)
- List<E> subList(int fromIndex, int toIndex); 返回fromIndex位置到toIndex位置的子集合,(包括fromIndex,不包括toIndex)
- ListIterator<E> listIterator(); 返回功能性更強的迭代器ListIterator
- ListIterator<E> listIterator(int index);
ListIterator接口
ListIterator接口繼承自Iterator接口,功能更加強大
- boolean hasPrevious(); 和 E previous(); 反向遍歷集合使用
- int nextIndex(); 和int previousIndex(); 返回索引位置
- void set(E e); 用元素e替換當前索引位置的元素
- void add(E e); 向集合中添加元素
AbstractList 抽樣類
- 需要子類復寫的兩個方法get(int index)和size() 方法。
- void add(int index, E element)、E remove(int index) 與E set(int index, E element)都是直接拋出異常。也就是說子類不復寫這三個方法,是不能修改集合元素的。
- 它有一個非常重要的成員屬性modCount。它的作用是在多線程環境中,判斷某個正在遍歷的集合,是否被別的線程修改了。如果是,那么就會拋出ConcurrentModificationException異常。
- 它有兩個內部迭代器實現類Itr和ListItr
內部類Itr和ListItr
- 使用cursor成員屬性 表示索引位置
- 使用lastRet成員屬性 表示迭代器遍歷到的元素索引位置
- 使用 expectedModCount 來判斷原集合是否被修改,拋出ConcurrentModificationException異常。
- 調用List的get(int index)方法獲取集合元素
- 調用List的set(int index, E element)方法替換元素
- 調用List的remove(int index)方法刪除元素
例子
public static void main(String[] args) {
// 創建AbstractCollection實例,需要復寫iterator和size,這是一個不可修改集合
List<Integer> list = new AbstractList<Integer>() {
int[] data = {1,2,3,4,5}; // 集合中的元素
@Override
public int size() {
return data.length;
}
@Override
public Integer get(int index) {
return data[index];
}
};
// 遍歷集合
for (int i : list) {
System.out.println(i);
}
// 測試contains方法
System.out.println(list.contains(3));
System.out.println(list.contains(10));
List<Integer> other = new ArrayList<>();
other.add(2);
other.add(3);
other = other.subList(0,1);
for (int i : other) {
System.out.println(i);
}
// 測試containsAll方法
System.out.println(list.containsAll(other));
// 查詢元素對應索引位置
System.out.println(list.indexOf(2));
}
qw