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);

接口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异常。也就是说这个异常是在迭代器中抛出的。

添加元素

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集合,将每个元素添加到本集合中。

删除元素

删除元素的方法,大部分使用AbstractCollection类提供的实现。

public E remove(int index) {
    throw new UnsupportedOperationException();
}

AbstractList是个不可修改的集合,所以这里做了限制。

更新元素

public E set(int index, E element) {
    throw new UnsupportedOperationException();
}

查询方法

1、boolean contains(Object o)和 boolean containsAll(Collection<?> c)方法采用AbstractCollection类提供的实现。

2、AbstractList提供了两个迭代器子类 Itr 和 ListItr ,之后我们将来分析它。

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);
}

3、get(int index)方法强制子类必须实现。

abstract public E get(int index);

4、查找某个元素在集合中的位置,如果没找到就返回-1。这里就要用到ListIterator迭代器了,因为它可以集合中的位置索引。

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;
}

注意当我们正向遍历集合的时候,previousIndex()方法返回的就是当前元素的位置,而并不是前一个元素的位置。这个时候nextIndex()方法返回的是下一个元素的位置。

5、反向查找某个元素在集合中的位置,如果没找到就返回-1。

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;
}

注意当我们反向遍历集合的时候,nextIndex()方法返回的就是当前元素的位置,而并不是下一个元素的位置。这个时候previousIndex()方法返回的是前一个元素的位置。

6、其它重要方法

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我们来了解怎样简单地实现一个迭代器。

1、成员属性

// 光标索引位置,0表示在集合开始位置(因为这是一个list集合,所以可以用索引表示开始位置)
int cursor = 0;
// 表示读取到的当前元素位置
int lastRet = -1;
// 用来判断原集合是否被修改,抛出ConcurrentModificationException异常。
int expectedModCount = modCount;

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,指向下一个元素。
  • 最后返回获取的元素。

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集合的反向遍历,以及添加和替换集合中元素的方法。

1、构造函数

ListItr(int index) {
    cursor = index;
}

表示从index - 1位置开始,反向遍历集合元素。

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光标就表示当前元素的索引位置
  • 最后返回获取的元素。

3、返回索引位置

// 如果是正向遍历集合,nextIndex返回值表示集合中下一个元素的索引位置。
// 如果是反向遍历集合,nextIndex返回值表示集合中当前元素的索引位置。
public int nextIndex() {
    return cursor;
}

// 如果是正向遍历集合,previousIndex返回值表示集合中当前元素的索引位置。
// 如果是反向遍历集合,previousIndex返回值表示集合中前一个元素的索引位置。
public int previousIndex() {
    return cursor-1;
}

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、添加元素

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),对它的操作其实都是调用父集合对应方法,本质上子集合就是父集合一段区间的投影,而并不是独立的。

1、成员属性

// 父集合的引用
private final AbstractList<E> l;
// 偏移量
private final int offset;
// 子集合的大小
private int size;

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值一样。

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));
}

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要自增。

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);
    }
}

 

posted @ 2022-01-24 08:32  残城碎梦  阅读(82)  评论(0编辑  收藏  举报