容器(二):Collection三足鼎立之List

容器(二):Collection三足鼎立之List

标签: Java编程思想


容器类中Collection接口和Map接口双雄并起,在Collection家族中也呈现着三足鼎立的局面:List接口、Set接口、Queue接口。

在 《容器(一):容器双雄之Collection》中介绍了Collection的相关知识,这篇笔记来学习 Collection 的子接口之一 List 接口

List接口

一个List是一个元素有序的、可以重复、可以为null的集合(有时候我们也叫它“序列”)。集合中的每个元素都有其对应的顺序索引。List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

为什么 List 中的元素 “有序”、“可以重复”呢?

首先,List 的数据结构就是一个序列,存储内容时直接在内存中开辟一块连续的空间,然后将空间地址与索引对应。

在JDK API中:

有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。

可以看出,List 接口的实现类在实现插入元素时,都会根据索引进行排列。比如:ArrayList是实现了基于动态数组的数据结构,LinkedList基于双链表的数据结构。

由于 List 的元素在存储时互不干扰,没有什么依赖关系,自然可以重复(这点与 Set 有很大区别)。

List接口的方法

与Collection接口类似:List接口中的方法并没有实现,而是交给其子接口去具体实现

List 接口中除了继承 Collection 接口中的方法以外,还提供以下类型的操作:

  • 位置相关: List 和 数组一样,都是从 0 开始,我们可以根据元素在 list 中的位置进行操作,比如说 get, set, add, addAll, remove;

  • 搜索相关: 从 list 中查找某个对象的位置,比如 indexOf, lastIndexOf;

  • 特殊迭代: 使用 Iterator 的拓展版迭代器 ListIterator 进行迭代操作;

  • 范围操作: 使用 subList 方法对 list 进行任意范围的操作。

下面我们逐一的研究一下List特有的这些方法:

测试代码

public static void testList1() {
        List list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add(new String("AA"));
        list.add(new String("BB"));
        System.out.println(list);   // [123, 456, AA, BB]

        list.add(0,555);
        System.out.println(list);   // [555, 123, 456, AA, BB]

        Object obj = list.get(1);
        System.out.println(obj);    // 123

        list.remove(0);
        System.out.println(list.get(0));    // 123

        list.set(0,111);
        System.out.println(list.get(0));    // 111

        list.add(111);
        System.out.println(list.indexOf(111));  // 0
        System.out.println(list.lastIndexOf(111));  // 4

        List list1 = list.subList(0,3);
        System.out.println(list1);  // [111, 456, AA]

    }

位置相关

List接口提供了一些与位置相关的方法:比如在指定位置添加元素、添加其他容器、用来获取和更改的get() set()方法,以及remove方法等。

  • void add(int index,E element)

    在列表的指定位置插入指定元素(可选操作)。将当前处于该位置的元素(如果有的话)和所有后续元素向右移动(在其索引中加 1)。
    参数:
    index - 要在其中插入指定元素处的索引
    element - 要插入的元素

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

    将指定 collection中的所有元素都插入到列表中的指定位置(可选操作)。将当前处于该位置的元素(如果有的话)和所有后续元素向右移动(增加其索引)。
    参数:
    index - 将指定 collection 的第一个元素所插入位置的索引
    c - 包含要添加到此列表的元素的 collection
    返回:
    如果此列表由于调用而发生更改,则返回 true

  • E get(int index)

    返回列表中指定位置的元素。
    参数:
    index - 要返回的元素的索引
    返回:
    列表中指定位置的元素

  • E set(int index,E element)

    用指定元素替换列表中指定位置的元素(可选操作)。
    参数:
    index - 要替换的元素的索引
    element - 要在指定位置存储的元素
    返回:
    以前在指定位置的被覆盖的元素

  • E remove(int index)

    移除列表中指定位置的元素(可选操作)。将所有的后续元素向左移动(将其索引减 1)。返回从列表中移除的元素。
    参数:
    index - 要移除的元素的索引
    返回:
    以前在指定位置的被移除的元素

  • boolean removeAll(Collection<?> c)

    从列表中移除指定 collection 中包含的其所有元素(可选操作)。
    指定者:
    接口 Collection 中的 removeAll
    参数:
    c - 包含从此列表中移除的元素的 collection
    返回:
    如果此列表由于调用而发生更改,则返回 true

下面是一个简单的 List 中的元素交换方法:

public static <E> void swap(List<E> a, int i, int j) {
    E tmp = a.get(i);
    a.set(i, a.get(j));
    a.set(j, tmp);
}

搜索相关

提供了搜索某一元素的方法,并返回该元素的索引。

  • int indexOf(Object o)

    返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。更确切地讲,返回满足 (o==null ? get(i)==null : o.equals(get(i))) 的最低索引 i;如果没有这样的索引,则返回 -1。
    参数:
    o - 要搜索的元素
    返回:
    此列表中第一次出现的指定元素的索引,如果列表不包含该元素,则返回 -1

  • int lastIndexOf(Objecto)

    返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。更确切地讲,返回满足 (o==null ? get(i)==null : o.equals(get(i))) 的最高索引 i;如果没有这样的索引,则返回 -1。
    参数:
    o - 要搜索的元素
    返回:
    列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1

特殊迭代

关于ListIterator要另写一篇笔记

  • ListIterator<E> listIterator()

    返回此列表元素的列表迭代器(按适当顺序)。
    返回:
    此列表元素的列表迭代器(按适当顺序)

  • ListIterator<E> listIterator(int index)

    返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。指定的索引表示 next 的初始调用所返回的第一个元素。previous 方法的初始调用将返回索引比指定索引少 1 的元素。
    参数:
    index - 从列表迭代器返回的第一个元素的索引(通过调用 next 方法)
    返回:
    此列表中元素的列表迭代器(按适当顺序),从列表中的指定位置开始

    范围操作

List.subList(int fromIndex, int toIndex) 方法返回 List 在 fromIndex 与 toIndex 范围内的子集。注意是左闭右开,[fromIndex,toIndex)。

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

返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分。(如果 fromIndex 和 toIndex 相等,则返回的列表为空)。返回的列表由此列表支持,因此返回列表中的非结构性更改将反映在此列表中,反之亦然。返回的列表支持此列表支持的所有可选列表操作。
参数:
fromIndex - subList 的低端(包括)
toIndex - subList 的高端(不包括)
返回:
列表中指定范围的视图

注意:

List.subList 方法并没有像我们想的那样:创建一个新的 List,然后把旧 List 的指定范围子元素拷贝进新 List。
真相是: subList 返回的仍是 List 原来的引用,只不过把开始位置 offset 和 size 改了下,

见 List.subList() 在 AbstractList 抽象类中的实现:

public List<E> subList(int start, int end) {
    if (start >= 0 && end <= size()) {
        if (start <= end) {
            if (this instanceof RandomAccess) {
                return new SubAbstractListRandomAccess<E>(this, start, end);
            }
            return new SubAbstractList<E>(this, start, end);
        }
        throw new IllegalArgumentException();
    }
    throw new IndexOutOfBoundsException();
}

SubAbstractListRandomAccess 最终也是继承 SubAbstractList,直接看 SubAbstractList:

    SubAbstractList(AbstractList<E> list, int start, int end) {
        fullList = list;
        modCount = fullList.modCount;
        offset = start;
        size = end - start;
    }

可以看到,的确是保持原来的引用。

所以,重点来了!

由于 subList 持有 List 同一个引用,所以对 subList 进行的操作也会影响到原有 List,举个栗子:

List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");

List subList  = list.subList(0,2).clear();
Sysout.out,println(list);

运行结果是:

[c,d]

注意这行代码:List.subList(fromIndex, toIndex).clear();
一句话实现了删除 List 中 subList 部分元素的操作。

还可以查找某元素在局部范围内的位置:

int i = list.subList(fromIndex, toIndex).indexOf(o);
int j = list.subList(fromIndex, toIndex).lastIndexOf(o);

List 与 Array 区别

List 在很多方面跟 Array 数组感觉很相似,尤其是 ArrayList,那 List 和数组究竟哪个更好呢?

相似之处:

  • 都可以表示一组同类型的对象
  • 都使用下标进行索引

不同之处:

  • 数组可以存任何类型元素
  • List 不可以存基本数据类型,必须要包装
  • 数组容量固定不可改变;List 容量可动态增长
  • 数组效率高; List 由于要维护额外内容,效率相对低一些
  • 容量固定时优先使用数组,容纳类型更多,更高效。

在容量不确定的情景下, List 更有优势,ArrayList 和 LinkedList 中都实现了容量动态增长,在专门的笔记中记录

List 与 Array 之间的转换

在 List 中有两个转换成数组的方法:

  • Object[] toArray()
    返回一个包含 List 中所有元素的数组;

  • T[] toArray(T[] array)
    作用同上,不同的是当 参数 array 的长度比 List 的元素大时,会使用参数 array 保存 List 中的元素;否则会创建一个新的 数组存放 List 中的所有元素;

数组工具类 Arrays 提供了数组转成 List 的方法 asList :

@SafeVarargs
public static <T> List<T> asList(T... array) {
    return new ArrayList<T>(array);
}

使用的是 Arrays 内部创建的 ArrayList 的转换构造函数:

    private final E[] a;
    ArrayList(E[] storage) {
        if (storage == null) {
            throw new NullPointerException("storage == null");
        }
        //直接复制
        a = storage;
    }

ps:用心学习,喜欢的话请点赞 (在左侧哦)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值