天天看点

轻松解读源码系列之Java集合接口&抽象类(3)—List、Set、Queue

作者:程序员xiao熊

大家好,我是程序员xiao熊,本篇内容将为大家分享Java集合接口Collection的三大子接口:List、Set、Queue;这些接口和抽象类是Java集合框架的基础,为具体的子类实现提供了规范以及基础实现,降低了子类实现的成本;本篇包含内容如下:

  1. List接口
  2. Set接口
  3. Queue接口
  4. 扩展阅读
轻松解读源码系列之Java集合接口&抽象类(3)—List、Set、Queue

Collection的三大子接口

1、List接口

1.1、List接口介绍

List是一个有序的集合(也称为序列),继承自Collection,Collection是直接父接口;其具备了如下规范和特性:

  • 开发者可以很精确的控制在哪个位置插入新元素,同时也可以通过整型索引下标来访问元素,以及查询列表中的元素;
  • List允许元素重复;更正确的说应该是List允许满足e1.equals(e2)的两个元素存在,也允许存在多个null元素(如果实现类允许存在null元素的话);但是也有例外,有的List是不允许元素重复,此时当开发者添加重复的元素时,会抛出异常;
  • List接口除了从Collection继承的规则约束之外,对iterator, add, remove, equals, 以及hashCode方法还有一些约束规则;
  • List接口提供了四个方法用于通过索引访问元素;List与数组一样,下标是从0开始的;需要注意的是,在某些实现中(例如LinkedList类),通过索引访问元素的执行时间与索引值成正比(具体原因参考《轻松解读源码系列之Java集合LinkedList(下)》的总结d的第3点),性能会随着元素数量增加而降低。
  • List接口提供了一个特殊的Iterator:ListIterator,ListIterator除了Iterator提供的方法之外,还提供了添加元素、更新元素以及双向访问元素的功能;并且还提供了一个方法用于获取指定了索引位置的ListIterator;
  • List接口提供了两个方法用于快速地在列表的任意位置添加或者删除多个元素;

子类实现接口时需要注意:

  • List的具体实现可能会不允许元素重复,这时添加元素会抛出异常,我们需要尽量避免这种实现方式,因为这并不太符合List的使用场景;
  • 一些List的实现类对于包含的元素会有一些约束;例如,一些List实现不支持null元素,以及限制了数据元素类型;当添加一个不符合条件的元素,则一般会抛出非检查异常(一般是 NullPointerException 或者ClassCastException);
  • 查询一个不符合规范的元素,也许会抛出异常,也许直接返回false;此外,尝试对不符合条件的元素进行操作时,首先操作的完成不会导致不符合条件的元素插入到集合中,但操作可能会抛出异常,也可能会执行成功(不符合条件的元素不会进入集合,只是方法执行成功),具体取决于实现方式。

使用的注意事项:

  • 如果调用者不知道所使用的列表的底层原理的话,遍历链表中的元素通常比索引的方式更好一点。
  • List接口提供了两个方法用于查询特定的对象;从性能的角度来看,这些方法应该谨慎使用。在许多实现中,它们将执行代价高昂的线性搜索。
  • 虽然列表允许包含自己作为元素,但还是要格外小心:equals和hashCode方法已经不适用这样的列表了。

1.2、List接口方法说明

boolean add(E e) 将元素添加至列表尾部
void add(int index, E element)(新增) 在指定位置添加元素
boolean addAll(Collection<? extends E> c) 添加一个集合中的元素到当前集合的尾部
boolean addAll(int index, Collection<? extends E> c) (新增) 在指定位置添加指定集合中的所有元素至当前集合的尾部
void clear() 清除集合元素
boolean contains(Object o) 判断是否包含指定的对象
boolean containsAll(Collection<?> c) 判断是否包含集合中的所有元素
boolean equals(Object o) 判断对象是否与当前集合相等,要求:集合的元素相等、元素顺序相同
E get(int index) (新增) 获取指定索引位置的元素
int hashCode()

返回集合的hashCode;逻辑如下:

int hashCode = 1;

for (E e : list)

hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

int indexOf(Object o) (新增) 获取指定对象在列表中的索引位置(从头部开始)
boolean isEmpty() 判断结合是否为空
Iterator<E> iterator() 返回列表的iterator对象
int lastIndexOf(Object o) (新增) 返回指定对象在列表的索引位置(从尾部开始)
ListIterator<E> listIterator()(新增) 返回列表的listiterator
ListIterator<E> listIterator(int index) (新增) 返回定位在指定索引位置处的listiterator
E remove(int index) (新增) 删除指定所以位置处的元素
boolean remove(Object o) 删除指定对象
boolean removeAll(Collection<?> c) 删除存在于指定集合中的元素
default void replaceAll(UnaryOperator<E> operator) (新增) 根据原来的元素计算新的元素,并进行替换
boolean retainAll(Collection<?> c) 保留指定集合中元素
E set(int index, E element) (新增) 更新指定索引位置处的元素
int size() 返回元素数量
default void sort(Comparator<? super E> c) (新增) 按照指定的comparator进行排序
default Spliterator<E> spliterator() 返回List的spliterator
List<E> subList(int fromIndex, int toIndex) (新增)

根据开始和结束下标获取子列表

注意:不包含下标范围,例如: [1, 6)

Object[] toArray() 将元素以Object数组形式返回,返回的数组与集合互不影响
<T> T[] toArray(T[] a) 返回指定类型的数组,返回的数组与集合互不影响

2、Set

2.1、Set接口介绍

Set接口是对数学上“集合”的抽象,是一个不会包含重复元素的集合,继承自Collection,Collection是其直接父级接口;更准确的说是set集合不会包含两个满足e1.equals(e2)的元素, 并且只有一个null元素;其具备以下规范和特性:

  • 除了从Collection继承的规则之外,Set接口对所有的构造函数、add、equals以及hashcode方法还增加了一些约束规则;所以Set接口再次声明了这些方法,而方法上声明的约束规则,仅仅只是针对Set接口的。Set接口对构造函数增加的约束规则是:所有的构造函数创建的集合是没有重复元素的集合;
  • 一部分Set接口的实现对其中的元素会有一些约束;例如,有一部分Set接口的实现禁止null元素,有一些实现类是对元素类型进行了限制;当尝试添加一个不符合规范的元素,则会抛出未检查异常,一般是NullPointerException 或者ClassCastException;
  • 查询一个不符合规范的元素,也许会抛出异常,也许直接返回false;此外,尝试对不符合条件的元素进行操作时,首先操作的完成不会导致不符合条件的元素插入到集合中,但操作可能会抛出异常,也可能会执行成功(不符合条件的元素不会进入集合,只是方法执行成功),这取决于具体的实现策略。

使用的注意事项:

在时如果将可变对象用作set元素,则必须非常小心。如果对象是集合中的一个元素,但其值的改变影响了等值比较,则集合的行为将会是不确定的。还有一个特殊情况是,不允许一个集合包含自己作为一个元素;

2.2、方法说明

Set接口对add、equals以及hashcode有一些新增的规则约束,具体的方法如红色字体部分

boolean add(E e) 添加元素,如果元素已存在,则返回false,集合保持不变;否则元素添加成功
boolean addAll(Collection<? extends E> c) 添加集合中的元素至当前集合,如果元素已存在,不添加;最终的结果就是两个set集合的并集;需要注意的是,如果在add过程中,入参集合c发生了变化,则addAll操作的结果是不确定的
boolean equals(Object o) 比较指定对象与此集合是否相等。如果指定对象也是一个集合,两个集合的大小相同,并且指定集合的每个成员都包含在这个集合中(或者等价地说,这个集合的每个成员都包含在指定集合中),返回true;这个定义确保了equals方法的逻辑在set接口的不同实现中都是正确的;
int hashCode() 返回此集合的哈希码值。集合的哈希码定义为集合中元素的哈希码之和,其中null元素的哈希码定义为零。这确保了对于任意两个集合s1和s2, s1.equals(s2)意味着s1. hashcode ()==s2. hashcode(),这符合Object.hashCode()的一般约定。
void clear() 清除集合中的元素
boolean contains(Object o) 判断是否包含某个元素
boolean containsAll(Collection<?> c) 判断是否包含指定集合中的所有元素
boolean isEmpty() 判断当前集合是否是空集合
Iterator<E> iterator() 返回当前集合的iterator对象
boolean remove(Object o) 删除指定的元素对象,如果元素存在,则返回true;否则返回false;
boolean removeAll(Collection<?> c) 删除当前集合中存在于指定集合中的元素;如果当前集合于指定的集合有交集,则返回true(说明当前集合会发生变化);否则返回false;
boolean retainAll(Collection<?> c) 保留存在于指定集合中的所有元素
int size() 返回集合中元素的数量
default Spliterator<E> spliterator() 返回集合的spliterator对象
Object[] toArray() 将元素以Object数组形式返回,返回的数组与集合互不影响
<T> T[] toArray(T[] a) 返回指定类型的数组,返回的数组与集合互不影响

3、Queue

3.1、Queue接口介绍

队列是在队头删除/获取元素,在队尾添加元素的线性表,在JDK中对应的接口是Queue;Queue继承自Collection接口,Collection是其直接父接口。除了基本的集合操作,Queue还提供了插入、获取(删除)和检索元素的操作。每类方法都有两种形式:一种在操作失败时抛出异常,另一种返回特殊值(null或false,取决于操作)。其中,insert操作返回特殊值的形式专门设计用于有容量限制的队列实现。在其他大多数实现中,insert操作不会失败。

队列方法总结
抛出异常 返回固定的值
Insert操作 add(e) offer(e)
Remove操作 remove() poll()
Examine操作(检索元素) element() peek()

元素顺序规则:

队列一般是按照FIFO的规则存放元素;但是在队列的实现类中,也有例外,例如PriorityQueue,它是按照元素的comparator的结果决定元素顺序,或者是元素本身的顺序。LIFO队列(或者是栈)也是例外,它是按照LIFO(后进先出)的规则存放元素。不管以什么排序规则存储元素,队列的头结点(第一个元素)是remove()或者poll()方法在被调用时需要删除的元素。在一个FIFO队列中,所有新加入的元素都会被添加到对列表尾部;其他类型的队列具体实现,可能会使用不同的存放元素的规则;对于队列的实现类来说,必须要指定存放元素排序规则;

实现队列的注意事项:

  • Queue接口并没有定义阻塞队列(blocking queue)的方法,阻塞队列通常用在并发编程中/并发环境中;阻塞队列的方法会阻塞等待队列中有元素,或者队列中有空间存储元素;阻塞队列的直接父级接口是Queue;
  • 队列的实现通常不会定义基于元素的版本的equals和hashCode方法,而是从Object类继承基于identity(身份/标识)的版本,因为元素相同但顺序属性不同的队列,基于元素的相等的规则并不是很好定义;

队列使用注意事项:

Queue的实现类通常不允许插入null值,不过也有例外的实现类:LinkedList;LinkedList是可以接收null值;但是即使实现类设计为可以接收null值,也最好不要把null值存入队列中,因为null值在某些场景下是有用的,例如poll方法通过返回null值,表示队列为空,

3.2、方法说明

由于Queue了继承了Collection, 也会拥有Collection的方法,约束和实现规范与Collection保持一致,具体参考Collection的说明;此外,Queue作为队列的实现,还提供了队列专用的方法,具体如下:

boolean add(E e) 在队列尾部添加元素,如果添加成功,则返回true,如果添加失败,则抛出异常
E element() 获取队列头结点元素,但不会删除;如果队列为空,会抛出异常
boolean offer(E e) 在队列尾部添加元素,如果添加成功,则返回true,如果添加失败,则返回false(这是与add()的区别)
E peek() 获取队列头结点元素,但不会删除;如果队列为空,则会返回null
E poll() 获取队列头结点元素,并删除;如果队列为空,则会返回null
E remove() 获取队列头结点元素,并删除;如果队列为空,则会抛出异常

4、扩展阅读

更多关于Java集合的内容,可参考以下文章:

  • 轻松解读源码系列之Java集合接口&抽象类(1)—Map和Collection
  • 轻松解读源码系列之Java集合接口&抽象类(2)—AbstractCollection
  • 轻松解读源码系列之Java集合ArrayList(上)
  • 轻松解读源码系列之Java集合ArrayList(下)
  • 轻松解读源码系列之Java集合LinkedList(上)
  • 轻松解读源码系列之Java集合LinkedList(中)
  • 轻松解读源码系列之Java集合LinkedList(下)
  • 轻松解读源码系列之ArrayList和LinkedList总结

欢迎关注【程序员xiao熊】,今天的分享就到这里,欢迎大家在评论区进行交流

继续阅读