今天的博客主题

       Java常用类 ——》Java集合类List接口


List

有序集合(也称为序列,列表)

使用这个接口可以精确地控制每个元素在列表中的插入位置。

可以通过其整数索引(在列表中的位置,类似于数组的下标)访问元素,并在列表中搜索元素。

与集合不同,列表允许有重复的元素。

List接口在Collection接口中指定的方法之外,在迭代器、add、remove、equals和hashCode方法的协定上又增添了一些自己的其他约定。为了方便起见,这里还包含了其他继承方法的声明。

List接口提供了一个特殊的迭代器,称为ListIterator,除了迭代器接口提供的常规操作之外,它还允许插入和替换元素以及双向访问。提供了一个方法来获取从列表中指定位置开始的列表迭代器。

有些列表实现对它们可能包含的元素有限制。

比如,有些实现禁止空元素,有些实现对元素的类型有限制。试图添加不符合条件的元素会引发未检查异常,通常是NullPointerException或ClassCastException。试图查询不符合条件的元素可能会抛出异常,或者只返回false。有些实现将显示前一种行为,有些将显示后一种行为。一般来说,尝试对不符合条件的元素执行操作,而该操作的完成不会导致将不符合条件的元素插入到列表中,这可能会引发异常,也可能会在实现时成功。此类异常在此接口的规范中被标记为“可选”。

 

List接口体系

java 接口中集合参数怎么接收_java 接口中集合参数怎么接收

ListIterator

列表迭代器,允许在任意方向遍历列表,在迭代过程中修改列表,并获得迭代器在列表中的当前位置。继承了Iterator迭代器接口。

ListIterator没有当前元素。它的光标位置总是位于调用previous()返回的元素和调用next()返回的元素之间。长度为n的列表的迭代器具有n+1可能的光标位置,如下面的插入符号(^)所示:

 

E(0)  E(1)  E(2)  E(3)  ...  E(n-1)
^     ^     ^     ^     ^    ^      ^

注意,remove和set(Object)方法不是根据光标位置定义的。

它们被定义为对调用next或previous()返回的最后一个元素进行操作的。

 

Collections

这个类由操作集合或返回集合的静态方法组成。它包含对集合进行操作的多态算法、“包装器”(它返回一个由指定集合支持的新集合)和其他的一些内容。

如果提供给该类的集合或类对象为空,该类的方法都将抛出NullPointerException异常信息。

该类中包含的多态算法的文档通常包括实现的简要描述。

这些描述被视为实现说明,而不是规范的一部分。

实现者应该可以随意替换其他算法,只要符合规范本身出现。(比如排序使用的算法不一定是归并排序,但必须是稳定的。)

此类中包含的“破坏性”算法,即修改其操作的集合的算法,被指定在集合不支持适当的变异原语(如set方法)时抛出UnsupportedOperationException。如果调用对集合没有影响,则这些算法可能(但不是必需)引发此异常。

比如对已排序的不可修改列表调用sort方法可能会或可能不会引发unsupportdoperationexception。

就是说这个类是对专门用来操作集合的一个类。

比如说可以使一个非同步的ArrayList 转成同步的ArrayList。等等...这种操作很多,具体自行看下

 

AbstractList

AbstractList 继承了 AbstractCollection 实现了 List 接口

这个类提供了List接口的框架实现,以尽可能减少实现这个接口所需的工作(比如使用ListIterator对集合的遍历),该接口由随机访问数据存储(如数组)支持实现。

对于顺序访问数据(如链表),应该优先使用AbstractSequentialList。

如果要实现一个不可修改的列表,只需要扩展这个类并为get(int)和size()方法提供实现。

如果要实现可修改的列表,必须重写set(int,Object),set(int,E)方法(否则将抛出unsupportdoperationexception)。

如果列表是可变大小的,必须重写add(int,Object),add(int,E)和remove(int)方法。

根据collection接口规范中的建议,通常应该提供一个void(没有参数)和collection构造函数。与其他抽象集合实现不同,不必提供迭代器实现。迭代器和列表迭代器由此类在随机访问方法之上实现。

此类中每个非抽象方法的文档详细描述了其实现。

如果正在实现的集合允许更有效的实现,则这些方法中的每一个都可能被重写。

 

ArrayList

ArrayList继承AbstractList 实现List<E>, RandomAccess, Cloneable, java.io.Serializable接口

ArrayList类描述

通过可调整大小的数组来实现的List接口。

实现所有可选的列表操作,并允许所有元素,包括null。

除了实现List接口之外,这个类提供了一些方法来操作内部用于存储列表的数组的大小。

(这个类大致相当于Vector,只是它不是同步。)

这里的size、isEmpty、get、set、iterator和listIterator操作时间复杂度在常量时间内运行。

add操作在摊销常数时间内运行,即添加n个元素需要O(n)的时间。

所有其他操作都在线性时间内运行(大致来说)。与LinkedList实现相比,常数因子较低。

每个ArrayList实例都有一个容量。容量是用于存储列表中元素的数组的大小。它总是至少和列表大小一样大。当元素被添加到ArrayList时,它的容量会自动增长。除了添加一个元素具有固定的摊余时间成本这一事实之外,增长方案的细节没有具体说明。

应用程序可以在使用ensureCapacity操作添加大量元素之前增加ArrayList实例的容量。这可能会减少增量重新分配的数量。

注意,此实现类不是同步的。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了列表,则必须在外部同步。(结构修改是说添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;如果是修改元素的值则不是结构修改)。这通常通过在自然封装列表的某个对象上进行同步来完成。

如果不存在这样的对象,则应该使用Collections.synchronizedList方法。

这最好在创建时执行此操作,以防止意外地对列表进行非同步访问:

List list = Collections.synchronizedList(new ArrayList(...));

这个类的迭代器和listIterator方法返回的迭代器是快速失败的:如果在迭代器创建后的任何时候对列表进行了结构上的修改,除了通过迭代器自己的remove或add方法之外,迭代器将抛出一个ConcurrentModificationException。因此,在面对并发修改时,迭代器会快速而干净地失败,而不是在将来某个不确定的时间冒着任意的、不确定的行为的风险。

注意,不能保证迭代器的fail-fast行为,因为通常情况下,在存在不同步的并发修改的情况下,不可能做出任何硬保证。fail-fast迭代器在尽最大努力的基础上抛出ConcurrentModificationException。因此,编写依赖于此异常的正确性的程序是错误的:迭代器的fail-fast行为应该只用于检测错误。

总结

1)底层是数组实现的(动态调整数组的大小)

2)允许元素为null

3)不是同步的,线程不安全

4)一般的操作时间复杂度在常量时间内运行O(n)

5)支持随机访问,按照下标访问元素

6)因是数组实现,中间插入删除元素效率低

7)容量不固定,但有最大值(int最大值-8)

8)

成员变量

/**
* 存储ArrayList元素的数组缓冲区。
* ArrayList的容量是这个数组缓冲区的长度。
* 任何空的ArrayList具有DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* 将在添加第一个元素时扩展为DEFAULT_CAPACITY。
* /
transient Object[] elementData;
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于默认大小的空实例的共享空数组实例。
* 与EMPTY_ELEMENTDATA的元素数据区分开来,以知道在添加第一个元素时要扩展多少容量。
* / 
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList的大小(包含的元素的数量)。
private int size;
/**
* 要分配的数组的最大大小。
* 一些vm在数组中保留一些标题词。
* 试图分配更大的数组可能会导致OutOfMemoryError:请求的数组大小超过VM限制
*/ 
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

为什么ArrayList容器最大是Integer.MAX_VALUE - 8?

构造方法

// 构造一个初始容量为10的空列表。
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
*  构造一个具有指定初始容量的空列表。
*  如果指定的初始容量为负,则抛出异常IllegalArgumentException
*/
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}
// 构造一个包含指定集合的元素的列表,按集合的迭代器返回元素的顺序排列。
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

ArrayList的动态扩容是怎么实现的呢?

常用API

别的方法就不一一罗列的,都是操作集合的。可以自行看下。

方法名都很规范。比如:添加一个元素就找add方法,删除一个元素找remove方法,看集合是不是空的就找isEmpty方法等等。。。

详细API地址:传送门

 

AbstractSequentialList

public abstract class AbstractSequentialList<E> extends AbstractList<E> {}

AbstractSequentialList 继承了 AbstractList。

AbstractSequentialList类描述

该类提供 List 接口的框架实现,以尽可能减少实现该接口所需的工作量,该接口由顺序访问数据存储(如链表)支持。对于随机访问数据(如数组),应该优先使用 AbstractList 。

这个类与AbstractList类相反,因为它实现了列表迭代器顶部的随机访问方法get(int index)、set(int index、E element)、add(int index、E element)和remove(int index)而不是反过来。

为了实现一个列表,程序员只需要扩展这个类并为 listIterator 和 size 方法提供实现。

对于不可修改的列表,程序员只需要实现列表迭代器的hasNext、next、hasPrevious、previous和index方法。

对于可修改的列表,应该实现列表迭代器的set方法。

对于可变大小的列表,还应该实现列表迭代器的remove和add方法。

根据集合接口规范中的建议,通常应该提供一个void(无参数)和集合构造函数。

AbstractSequentialList 提供的抽象方法,上述的方法都基本存在。

java 接口中集合参数怎么接收_迭代器_02

 

LinkedList

LinkedList继承AbstractSequentialList 实现List<E>, Deque<E>, Cloneable, java.io.Serializable接口

LinkedList 类描述

它是 List 和 Deque 接口的双链表实现,实现所有可选的列表操作,并允许所有元素(包括null)

所有操作的执行都与双链接列表的预期一样。索引到列表中的操作将从开始或结束遍历列表,以更接近指定索引的为准。

注意,这个实现不是同步的。如果多个线程同时访问一个链接列表,并且至少有一个线程在结构上修改了该列表,则它必须在外部同步。

结构修改是添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。

这通常是通过在自然封装列表的某个对象上进行同步来实现的。

如果不存在此类对象,则应使用 Collections.synchronizedList 方法包装列表。最好在创建时执行此操作,以防止意外地对列表进行非同步访问:List list = Collections.synchronizedList(new LinkedList(...));

这个类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:如果在迭代器创建后的任何时候对列表进行了结构修改,则迭代器将抛出一个ConcurrentModificationException,迭代器本身的remove或add方法除外。因此,在并发修改的情况下,迭代器会快速而干净地失败,而不是在将来某个不确定的时刻冒着任意的、不确定的行为的风险。

注意,不能保证迭代器的fail-fast行为,因为通常情况下,在存在不同步的并发修改时,它不可能做出任何硬保证。Fail-fast迭代器在尽最大努力的基础上抛出ConcurrentModificationException。因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的快速失败行为应仅用于检测错误。

总结

1)LinkedList 是 List 接口的一个实现类。

2)LinkedList底层数据结构是通过双向链表实现的。

3)容量大,因为是链表,不会出现容量不足的问题

4)可以作为栈、队列、双端队列数据结构使用。

5)在插入和删除元素是效率高。

6)线程是不安全的

7)随机访问速度慢

常用API

API也是很简单,不多啰嗦。。。

详细API地址:传送门

 

Vector(向量)

public class Vector<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}

vector类描述

Vector 类实现一个可增长的对象数组。与数组一样,它包含可以使用整数索引访问的组件。但是, Vector 的大小可以根据需要增大或缩小,以适应在创建 Vector 之后添加和删除项。

每个向量都试图通过维护一个 capacity 和一个 capacitycrement 来优化存储管理。 capacity 始终至少与向量大小相同。它通常更大,因为随着组件被添加到向量中,向量的存储以块的形式增加,大小为capacitycrement。应用程序可以在插入大量组件之前增加向量的容量,这减少了增量重新分配的数量。

这个类的iterator()返回的迭代器和listIterator(int)迭代器方法是快速失败的。如果在迭代器以任何方式创建,除了通过迭代器自己的 remove 或 add方法,迭代器将抛出 ConcurrentModificationException,因此,面对并发修改时,迭代器会快速而干净地失败,而不是冒着任意的、不确定的行为未来的时间。由返回的Enumeration elements()方法是快速失败的。

注意,不能保证迭代器的快速失败行为一般说来,在存在未同步的并发修改。失效快速迭代器尽最大努力抛出 ConcurrentModificationException 。因此,编写依赖于此的程序是错误的正确性异常:迭代器的快速失败行为只用于检测错误。

从Java 2平台v1.2开始,这个类被修改为实现 List 接口,使其成为Java集合框架。

与新的集合实现不同, Vector 是同步的。

如果不需要线程安全的实现,建议使用 ArrayList 代替 Vector。

我们发现Vector实现类的体系结构和ArrayList是一样的。

不同的是:Vector是同步的,线程是安全的。

特点呢和ArrayList底层数据结构一样并且类实现也一样,其优点也一样 随机访问速度快,插入和移除性能较差 (这是数组的特点)。重要一点就是线程安全,缺点呢就是既然线程安全了,效率自然就低了。

常用API

详细API地址:传送门

 

Stack

public class Stack<E> extends Vector<E> {}

Stack类描述

Stack类表示后进先出(LIFO)对象堆栈。

它提供五个方法允许 Vector 被当作 Stack 的操作扩展了类Vector.

提供了通常的 push 和 pop 操作,以及在堆栈顶部项处查看 peek 方法、测试堆栈是否为空的方法,以及在堆栈中搜索项并发现距顶部有多远的方法。

首次创建堆栈时,它不包含任何项。

Deque 接口及其实现提供了一组更完整和一致的后进先出堆栈操作,应优先于此类使用。

例如:Deque<Integer>stack=new ArrayDeque<Integer>;

Stack类除了构造函数,只提供了5个方法来操作Stack 。

 

集合算法

Collections该类中的大多数多态算法专门适用于List。拥有所有这些算法可轻松处理List。

sort:List使用合并排序算法对进行排序,该算法可提供快速,稳定的排序。(稳定排序是一种不会对相等元素进行重新排序的排序。)
shuffle:随机排列中的元素List。
reverse:反转中元素的顺序List。
rotate:将所有元素旋转List指定距离。
swap:交换元素中指定位置的元素List。
replaceAll:将所有出现的一个指定值替换为另一个。
fill:List用指定的值覆盖a中的每个元素。
copy:将源复制List到目标List。
binarySearch:List使用二进制搜索算法按顺序搜索元素。
indexOfSubList:返回一个List等于另一个的第一个子列表的索引。
lastIndexOfSubList:返回一个List等于另一个的最后一个子列表的索引。

 

大总结

List 是一个有序集合接口,元素可以重复。

提供了列表迭代器 ListIterator。

其实现有 ArrayList,LinkedList,Vector

 

ArrayList

ArrayList是用数组实现的,该对象存放在堆内存中,数组的内存是连续的,不存在相邻元素之间还隔着其他内存。底层是一个可动态扩容的数组。容量限制不大于Integer.MAX_VALUE - 8的小大,每次扩容1.5倍。ArrayList 查询快,增删慢,但ArrayList线程是不安全的。

如果想保证 ArrayList 线程的安全性,可以考虑使用CopyOrWriteArrayList或者使用collections.synchronizedList(Lise l)函数返回一个线程安全的ArrayList类。

ArrayList 不自定义位置添加元素和LinkedList性能没区别,ArrayList默认元素追加到数组后面,而LinkedList只需要移动指针,所以两者性能相差无几。

如果ArrayList自定义位置插入元素,越靠前,需要重写排序的元素越多,性能消耗越大,LinkedList无论插入任何位置都一样,只需要创建一个新的表项节点和移动一下指针,性能消耗很低。

ArrayList 是基于数组,查看任意位置元素只需要获取当前位置的下标就可以,效率高,LinkedList获取元素需要从最前面或者最后面遍历到当前位置元素获取,如果集合中元素很多,就会效率很低,性能消耗大。

在新建ArrayList的时候,JVM为其分配一个默认或指定大小的连续内存区域(封装为数组)。

索引ArrayList时,速度比原生数组慢是因为要用get方法,是一个函数调用,而数组直接用[下标]访问,相当于直接操作内存地址,速度当然比函数调用快。

增加元素会检查容量,不足则创建新的连续内存区域(大小等于初始大小+步长),也用数组形式封装,并将原来的内存区域数据复制到新的内存区域,然后再用ArrayList中引用原来封装的数组对象的引用变量引用到新的数组对象:elementData = Arrays.copyOf(elementData, newCapacity);

ArrayList里面的removeIf方法就接受一个Predicate参数,采用如下Lambda表达式就能把,所有null元素删除:list.removeIf(e -> e == null);

ArrayList只能包含对象类型。对于基本类型数据,集合使用自动装箱来减少编码工作量。

ArrayList可以存放任何不同类型的数据(因为它里面存放的都是被装箱了的Object型对象,ArrayList内部就是使用"object[] _items;"这样一个私有字段来封装对象的)。

ArrayList也采用了快速失败(Fail-Fast机制)的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。具体介绍请参考HashMap的实现原理中的Fail-Fast机制。

为什么ArrayList的elementData是用transient修饰的?

transient修饰的属性意味着不会被序列化,在序列化ArrayList的时候,不序列化elementData。

为什么要这么做呢?

elementData不总是满的,每次都序列化,会浪费时间和空间。

一般建议在单线程中使用ArrayList,在频繁遍历查看元素的时候使用 ArrayList。

 

LinkedList

LinkedList 底层是链表(循环双向链表),不需要维护容量大小。顺序访问。元素存储本身内存地址的同时还存储下一个元素的地址,增删快,查找慢,不是同步的,线程不安全。

因为底层为链表结构,查询时需要从头节点(或尾节点)开始遍历所以查询效率差;但同时也因为是链表结构,做插入、删除操作时只要断开当前删除节点前驱、后驱引用,并将原来的前、后节点的引用链接起来,所以效率高。

LinkedList是一个继承于AbstractSequentialList的双向链表,它可以被当堆栈(stack),队列(queue)或双向队列(deque)来操作。

如果多个线程同时访问一个LinkedList,则必须自己实现访问同步。一种解决方法是在创建LinkedList时构造一个同步的LinkedList:List list = Collections.synchronizedList(new LinkedList(…));

LinkedList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

一般在频繁插入删除元素使用 LinkedList。最好不要使用普通for循环遍历LinkedList,不然你会崩溃的!

可以选择使用foreach或迭代器来进行遍历操作。

 

Vector

Vector 底层的数据结构数组,基于线程安全的,线程是同步的,在查询和增删元素时效率都很低。

Vector 支持自定义设置增长的大小,ArrayList没有提供相关的方法。

ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以索引数据快,插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快。

Vector 太过古老,被ArrayList取代,Stack 也已经被ArrayDeque所取代。

 

一句话概括

List是一个接口,继承Collection接口,是一个有序可重复的集合。

ArrayList底层数据结构是数组,查询快,增删慢。线程不安全,效率高。(比较常用)

LinkedList底层数据结构是链表,查询慢,增删快。线程不安全,效率高。

Vector底层数据结构是数组,查询快,增删慢。线程安全,效率低。

Stack堆栈,继承Vector。特点是:LIFO (后进先出)


在面试的时候这样回答,很容易被怼的体无完肤!!!