List源码阅读笔记

前言

List接口是Collection接口的子接口之一,Collection主要提供一些集合通用的方法,而List则是jdk针对列表/线性表这种更加具体的集合进行抽象。List数据结构有如下特点:

1)元素可以通过位置检索访问;

2)用户可以在指定位置插入数据;

3)元素可以为null、可以重复;

4)元素之间是有序的(线性表元素之间有相对的前后次序),因此List集合是一类有序集合。

 

一、类定义

public interface List<E> extends Collection<E>

二、方法定义

既然List是继承Collection接口的子接口,我们主要分析其特有的方法即可。不过List也重写了Collection的spliterator()方法:

    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.ORDERED);
    }

方法设置Spliterator的元素的特性为Spliterator.ORDERED,与Collection的不同。

下面再来看List特有的方法,List针对线性表结构,提供了对位置以及子表的操作。

(一)元素的增删改查

(1)

void add(int index, E element);

添加指定元素到列表的指定位置处,将该位置处及其后面的所有元素移动到指定元素的右边(即,将它们的索引加1)

当列表不支持此操作时,抛出UnsupportedOperationException(也就是说,此操作是可选操作);

当指定元素被禁止添加到此集合中时,抛出ClassCastException;

当指定元素为null并且此集合不支持null元素时,则抛出NullPointerException;

当指定的某些特性阻止该元素被添加到集合中,抛出IllegalArgumentException;

当指定索引超出集合范围(index<0 或者 index>size()),抛出IndexOutOfBoundsException

(2)

E remove(int index);

删除列表中指定位置的元素,将该位置处及其后面的所有元素往左边移动一位(即,将它们的索引减1)

当列表不支持此操作时,抛出UnsupportedOperationException(也就是说,此操作是可选操作);

当指定索引超出集合范围(index<0 或者 index>size()),抛出IndexOutOfBoundsException

(3)

E set(int index, E element);

用指定元素更新列表中指定位置处的元素,并返回该位置处的旧元素(被替代的元素)

当列表不支持此操作时,抛出UnsupportedOperationException(也就是说,此操作是可选操作);

当指定元素被禁止添加到此集合中时,抛出ClassCastException;

当指定元素为null并且此集合不支持null元素时,则抛出NullPointerException;

当指定的某些特性阻止该元素被添加到集合中,抛出IllegalArgumentException;

当指定索引超出集合范围(index<0 或者 index>size()),抛出IndexOutOfBoundsException

(4)

E get(int index);

返回列表中指定位置处的元素

当指定索引超出集合范围(index<0 或者 index>size()),抛出IndexOutOfBoundsException

(5)

int indexOf(Object o);

查询指定元素在列表中首次出现的索引

如果存在指定元素,则返回指定元素在列表中首次出现的索引;如果不存在,则返回-1

也就是说,此方法要么返回满足o==null ? get(i)==null : o.equals(get(i))成立的最低索引,要么就是没有满足上述条件的元素,返回-1

当指定元素与列表不兼容时,抛出ClassCastException;

当指定元素为null并且此列表不支持null元素时,则抛出NullPointerException。

(6)

int lastIndexOf(Object o);

查询指定元素在列表中最后一次出现的索引

如果存在此元素,则返回指定元素在列表中最后一次出现的位置;如果不存在此元素,则直接返回-1
也就是说,此方法要么返回满足o==null ? get(i)==null : o.equals(get(i))成立的最高索引,要么就是没有满足上述条件的元素,返回-1

当指定元素与列表不兼容时,抛出ClassCastException;

当指定元素为null并且此列表不支持null元素时,则抛出NullPointerException。

(二)批量修改

(1)

boolean addAll(int index, Collection<? extends E> c);

在指定位置处插入指定集合的所有元素,并将指定位置处及其后面的所有元素的索引增加一定的大小,添加这些元素时按照指定集合的迭代器迭代顺序添加。

当调用此方法之后,集合的内容被改变了,则此方法返回true。

我们知道,List接口继承了Collection接口诸多批量操作的方法,如,removeAll(),clear(),addAll()。这里的addAll()方法与继承的addAll()方法相比,多了一个索引参数index,也就是指定批量插入的位置。这符合线性表有序的特性,而Collection没有这个方法是因为Collection接口是有序集合、无序集合的总接口,无序集合没有索引,所以Collection中不能有索引相关的方法。

注意:当调用此方法的过程中,指定集合同时被修改,那么结果将不可控。比如,一个集合调用此方法插入集合本身的所有元素,并且此集合不为空时。

当列表不支持此操作,抛出UnsupportedOperationException;

当指定集合的元素被禁止添加到此集合中时,抛出ClassCastException;

当指定集合包含null元素并且此集合不支持null元素时,或者,指定集合为null,则抛出NullPointerException;

当指定集合的一个元素的某些特性阻止该元素被添加到集合中,抛出IllegalArgumentException;

当指定索引超出集合范围(index<0 或者 index>size()),抛出IndexOutOfBoundsException

(2)

    
    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }

默认实现的方法,通过迭代器遍历每个元素,并对它们进行指定的操作,然后将操作的结果代替原来的元素。

由于里面代替过程是通过列表迭代器的set()方法实现的,所以,如果列表的列表迭代器如果不支持set()操作,那么将抛出UnsupportedOperationException。

(三)其他方法

(1)

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

对列表的所有元素进行排序。

此方法根据指定的规则(指定的比较器)对当前列表的所有元素进行排序。可以看到,排序过程是通过Arrays的sort()方法实现的。另外,排序是在利用toArray()返回的一个新数组上面进行的,然后再利用列表迭代器的set()方法,将排序后的新数组的每个元素一一更新到列表的对应位置处。

(2)

ListIterator<E> listIterator();

返回一个在列表元素之上的列表迭代器

ListIterator继承自Iterator,主要增加了向前遍历的功能,以及对集合元素进行更新的set()操作
(3)

ListIterator<E> listIterator(int index);

返回一个从指定位置处开始(上面的方法默认从0开始),在列表元素之上的列表迭代器

指定位置指向的是在列表迭代器创建之后,第一次调用这个列表迭代器的next()方法时,返回的第一个元素的位置。同时,指定位置也是第一次调用这个列表迭代器的previous()方法时,返回的第一个元素的位置减1.

我们可以看到,List接口除了继承自Collection接口的iterator()方法之外,还有listIterator()方法,这是为了满足线性表前后遍历的特性所抽象出的操作。

(4)

List<E> subList(int fromIndex, int toIndex);

返回列表的在[fromIndex, toIndex)范围内的一个子集合,返回的列表支持原列表的所有操作。如果fromIndex等于toIndex,那么返回的是一个空列表。

返回的子列表其实是原列表的一个视图,也就是说还是跟原列表共享一份数据。由于底层是共享的数据,所以列表中的非结构性更改(如,更新一个元素的值)将反映在返回的子列表中;反之亦然,如果子列表发生非结构性更改,那么原列表也将被更改。

此方法消除了显式范围操作,任何需要进行的列表范围操作都可以利用调用此方法返回的子列表,而不是直接操作整个列表。比如:list.subList(from, to).clear(); 就可以删除列表某个位置范围之间的元素。

上面的例子是通过返回的子列表对列表进行结构性修改(像改变列表大小的操作就是结构性修改),但是,如果原列表以任何方式进行结构修改,而不是通过返回的子列表,那么通过此方法返回的子列表的语义将变得undefined(不明确)。

当指定索引超出集合范围(index<0 或者 index>size()),抛出IndexOutOfBoundsException。

小结:

1、所谓的非结构性修改,是指不改变list大小的修改。相反,结构性修改就是指改变了list大小的修改。
1)非结构性修改会影响彼此;
2)至于结构性修改:
如果发生结构性修改的是子列表,那么列表的大小也会发生改变;
如果发生结构性修改的是原列表(不包括由于返回的子列表导致的改变),那么子列表语义上将是undefined。
在AbstractList(ArrayList的父类,List的子类)中,undefined的具体表现形式是抛出一个ConcurrentModificationException
因此,如果你在调用了sublist返回了子列表之后,如果修改了原列表的大小,那么之前产生的子列表将会失效,变得不可用

2、这里的子列表和原列表其实有点像数据库三层模式的外模式和模式的关系:模式是整个应用系统的数据模型的逻辑结构,包含所有数据,而外模式则是模式的部分视图,不同的应用程序/功能模块只能看到不同的数据范围所对应的的外模式。

 

posted @ 2019-07-05 21:32  JeremyChan  阅读(271)  评论(0编辑  收藏  举报