引言
集合在任何语言中都是比较重要的基础知识,不同的集合在实现上采用了各种不同的数据结构,导致了各个集合的性能以及使用方式上存在很大差异,深入了解集合框架的整体结构以及各个集合类的实现原理,并灵活使用各个集合对编码有很大帮助。
本系列文章从集合框架的整体设计到源码细节分析了java.util包下各个集合相关接口、抽象类以及各种常用的集合实现类,希望通过这个系列的文章对大家理解各个集合有一定帮助。
如未做特殊说明,本系列所有源码出自以下JDK环境:
java version “1.8.0_131”
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
接口框架
一个好的框架在设计上都会考虑将接口与实现分离,java.util也不例外。要快速理解java.util,第一步就是从他的接口设计入手,从接口的整体设计可以快速明确这个框架的作用和功能。
*图1 集合接口框架*
通过上面的接口框架图可以看出:Collection
Collection
1 | 复制代码public interface Collection<E> extends Iterable<E> |
Collection
1 | 复制代码int size(); |
在Collection
iterator()
1 | 复制代码Iterator<E> iterator(); |
iterator方法返回一个实现了Iterator接口的对象,作用是依次访问集合中的元素,Iterator
1 | 复制代码boolean hasNext(); |
通过多次调用next()方法可遍历集合中的所有元素,需要注意的是需要在调用next()之前调用hasNext()方法,并在hasNext()返回true的时候才可以调用next(),例如:
1 | 复制代码private static void collectionIterator() { |
从JDK5开始使用“for each”这种更加方便的方式来遍历集合,只要实现了Iterable接口,都可以使用“for each”来遍历,效果和使用iterator一样。Iterable接口只包含一个方法:
1 | 复制代码Iterator<E> iterator(); |
1 | 复制代码private static void foreachCollectionIterator() { |
toArray( ) 以及 toArray(T[ ] a)
toArray和toArray(T[ ] a)返回的都是当前所有元素的数组。
toArray返回的是一个Object[]数组,类型不能改变。
toArray(T[ ] a)返回的是当前传入的类型T的数组,更方便用户操作,比如需要获取一个String类型的数组:toArray(new String[0])。
List
1 | 复制代码public interface List<E> extends Collection<E> |
List
除了包含Collection
1 | 复制代码E get(int index); |
其中需要引起注意的地方是ListIterator这个类:
List
1 | 复制代码public interface ListIterator<E> extends Iterator<E> { |
ListIterator
- ListIterator
可以向后迭代previous() - ListIterator
可以获取前后索引nextIndex() - ListIterator
可以添加新值add(E e) - ListIterator
可以设置新值set(E e)
Set
1 | 复制代码public interface Set<E> extends Collection<E> |
Set
至于为什么要定义一个方法签名完全重复的接口,我的理解是为了让框架结构更加清晰,将集合从可以添加重复元素和不可以添加重复元素,可以通过整数索引访问和不可以通过整数索引这几点上区别开来,这样当程序员需要实现自己的集合时能够更准确的继承相应接口。
Map<K,V>
1 | 复制代码public interface Map<K,V> |
API说明上关于Map<K,V>的说明非常精炼:从键映射到值的一个对象,键不能重复,每个键至多映射到一个值。
从键不能重复这个特点很容易想到通过Set
1 | 复制代码int size(); |
java Set<K> keySet()
返回映射中包含的键集视图,是一个Setjava Collection<V> values()
返回映射中包含的值得集合视图,Collectionjava Set<Map.Entry<K,V>> entrySet()
返回映射中包含的映射集合视图,这个视图是一个Set
entrySet()返回的是一个Map.Entry<K,V>类型的集,Map.Entry<K,V>接口定义了获取键值、设置值的方法,定义如下:
1 | 复制代码interface Entry<K,V> { |
类框架
从一个框架的接口知道了这个框架的结构和功能,能够用来做什么。但具体怎么使用,怎么扩展,那就需要了解框架中实现这些接口的部分。
java.util提供了一系列抽象类,来实现上面的接口,这些抽象类中提供了大量基本的方法。如果需要实现自己的集合类,扩展这些抽象类比直接继承接口方便的多。
*图2 抽象类以及实现类框架*
除了图中的抽象类和具体实现类,还有部分历史版本遗留下来的类,包括Vetor,Stack,Hashtable,Properties等,在这里就不做说明,重点关注图中的类即可。
抽象类
需要关注几个关键的抽象类包括AbstractCollection
各个集合的关键区别就在每个集合所使用的数据结构和算法上,所以在抽象类层面都没有涉及具体的数据结构和算法,只对操作这些数据结构的方法做了基本实现。
AbstractCollection
1 | 复制代码public abstract class AbstractCollection<E> implements Collection<E> |
AbstractCollection
1 | 复制代码public abstract Iterator<E> iterator(); |
如果需要实现的是一个不可修改的集合,只需要实现iterator()和size()方法即可,如果需要实现一个可修改的集合,必须重写add(E e)方法。
在AbstractCollection
1 | 复制代码public boolean contains(Object o) { |
AbstractList
1 | 复制代码public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> |
AbstractList
1 | 复制代码abstract public E get(int index); |
没有实现的原因在于AbstractList
值得注意的是AbstractList
1 | 复制代码private class Itr implements Iterator<E> { |
AbstractSet
1 | 复制代码public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> |
AbstractSet
1 | 复制代码//AbstractSet<E> 中的 equals |
AbstractMap<K,V>
1 | 复制代码public abstract class AbstractMap<K,V> implements Map<K,V> |
AbstractMap<K,V>抽象类中实现了除entrySet()方法外的基本所有方法,其中返回键集的Set
这里使用视图的好处在于抽象类中你不需要确定返回的Set
keySet()的源码:
1 | 复制代码public Set<K> keySet() { |
总结
java.util这个框架的结构还是非常清晰的,从接口的分类,每个接口的抽象类实现,都很好的保证了框架的伸缩性,为后续的实现和自定义扩展提供了极大地方便。
除了上述的接口以及抽象类以外,java.util框架还提供了一些其他结构,在使用频率上不是太高,比如Queue
在后面的章节中我们会详细讲解几个关键集合的实现,从数据结构、算法以及性能等各方面分析孰优孰劣。
本文转载自: 掘金