> Java > java지도 시간 > Java의 핵심 기술 포인트인 컬렉션 프레임워크에 대한 자세한 소개

Java의 핵심 기술 포인트인 컬렉션 프레임워크에 대한 자세한 소개

黄舟
풀어 주다: 2017-03-22 10:55:15
원래의
1595명이 탐색했습니다.

개요

Java 컬렉션 프레임워크는 Java 클래스 라이브러리의 일련의 인터페이스, 추상 클래스 및 구체적인 구현 클래스로 구성됩니다. 여기서 말하는 컬렉션은 개체 그룹을 함께 구성한 다음 다양한 필요에 따라 데이터를 조작하는 것입니다. 컬렉션 유형은 이러한 개체를 보유하는 컨테이너입니다. 즉, 가장 기본적인 수집 기능은 개체 그룹을 모아 중앙 집중식으로 관리하는 것입니다. 컬렉션 유형은 컬렉션에 중복 개체가 허용되는지 여부, 개체가 특정 순서로 구성되는지 여부 등의 기준에 따라 다양한 하위 유형으로 세분화될 수 있습니다.

Java 컬렉션 프레임워크는 기본 메커니즘 세트와 이러한 메커니즘의 참조 구현을 제공하며, 기타 관련 인터페이스에는 Iterator 인터페이스가 포함됩니다. RandomAccess 인터페이스를 기다려주세요. 이러한 컬렉션 프레임워크의 인터페이스는 컬렉션 유형이 구현해야 하는 기본 메커니즘을 정의합니다. Java 클래스 라이브러리는 데이터 구성 및 사용에 대한 다양한 요구 사항에 따라 다양한 인터페이스만 구현하면 됩니다. . Java 클래스 라이브러리는 컬렉션 유형 함수의 부분 구현을 제공하는 일부 추상 클래스도 제공합니다. 이를 기반으로 자체 컬렉션 유형을 추가로 구현할 수도 있습니다.

컬렉션 인터페이스

Iterator

먼저 이 인터페이스의 정의를 살펴보겠습니다.

public interface Collection<E> extends Iterable<E>
로그인 후 복사

우선, 두 번째로 유형 매개변수를 사용합니다. Iterable 인터페이스의 정의를 살펴보겠습니다.

public interface Iterable<T> {
  Iterator<T> iterator();
}
로그인 후 복사

이 인터페이스는 Iterator< T> 타입 객체이므로 Iterator의 정의를 살펴보겠습니다:

public interface Iterator<E> { 
  boolean hasNext(); 
  E next(); 
  void remove();
}
로그인 후 복사

그러고보니 Iterator(Iterator)에 대해 간단히 살펴보겠습니다. 위에서 우리는 Iterable 인터페이스와 Iterator 인터페이스라는 총 두 가지 인터페이스를 언급했습니다. 문자 그대로 전자는 "반복 가능"을 의미하고 후자는 "반복자"를 의미합니다. 이런 식으로 Iterable 인터페이스를 구현하는 클래스는 iterable입니다. Iterator 인터페이스를 구현하는 클래스는 입니다. iterator는 컬렉션의 객체를 순회하는 데 사용하는 것입니다. 즉, 컬렉션의 경우 기본 유형 배열을 통해 순회하는 것처럼 배열 인덱스를 통해 해당 위치의 요소에 직접 액세스하지 않습니다. 이는 컬렉션 유형의 순회 동작이 순회되는 컬렉션 개체와 분리되어 컬렉션 개체의 반복자를 얻는 한 컬렉션 유형의 특정 구현에 신경 쓸 필요가 없다는 것입니다. 객체 순회 순서와 같은 세부 사항은 모두 해당 반복자에 의해 처리됩니다. 이제 앞서 언급한 내용을 정리하겠습니다. 먼저 Collection 인터페이스는 Iterable 인터페이스에서는 반복자 메서드를 구현해야 하며 이 메서드는 반복자 개체를 반환합니다. 반복자 객체는 Iterator 인터페이스를 구현하는 객체입니다. 이 인터페이스는 hasNext(), next() 및 delete() 메서드를 구현해야 합니다. 객체가 탐색되었는지 여부) 다음 메서드는 다음 요소를 반환합니다(다음 요소가 없는 경우 이를 호출하면 NoSuchElementException 예외가 발생합니다). 가장 최근 요소를 제거하는 데 사용됩니다. next 메소드를 호출하여 반환됩니다. (next 메소드를 호출하지 않고 제거 메소드를 직접 호출하면 오류가 보고됩니다.) 컬렉션 반복을 시작하기 전에 첫 번째 요소의 앞을 가리키는 포인터가 있다고 상상할 수 있습니다. 그 후, 이 포인터는 첫 번째 요소를 "스윕"하고 이를 반환합니다. 즉, 이 포인터 뒤에 요소가 있는지 확인하는 것입니다. , 이 포인터는 항상 방금 순회한 요소와 순회할 다음 요소를 가리킵니다. 일반적으로 컬렉션 개체를 반복하는 코드는 다음과 같습니다.

Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while (iter.hasNext()) {
  String element = iter.next();
  //do something with element
}
로그인 후 복사

Java SE 5.0부터 동등한 것을 사용할 수 있지만 위 코드 조각의 더 간결한 버전:

for (String element : c) {
  //do something with element
}
로그인 후 복사

위에서 Iterator 인터페이스의 제거 메서드는 다음 메서드가 요소를 반환한 후에 호출되어야 한다고 언급했습니다. 이는 제공된 Collection 인터페이스를 구현하는 클래스에 해당됩니다. Java 클래스 라이브러리에서 우리를 위해. 물론, Collection 인터페이스를 구현하는 컬렉션 클래스를 직접 정의함으로써 이 기본 동작을 변경할 수 있습니다(타당한 이유가 없는 한, 이렇게 하지 않는 것이 가장 좋습니다).

컬렉션 인터페이스

먼저 공식 정의를 살펴보겠습니다.

The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered and others unordered. The JDK does not provide any direct implementations of this interface: it provides implementations of more specific subinterfaces like Set
and List.

大概的意思就是:Collection接口是集合层级结构的根接口。一个集合代表了一组对象,这组对象被称为集合的元素。一些集合允许重复的元素而其他不允许;一些是有序的而一些是无序的。Java类库中并未提供任何对这个接口的直接实现,而是提供了对于它的更具体的子接口的实现(比如Set接口和List接口)。

我们知道,接口是一组对需求的描述,那么让我们看看Collection接口提出了哪些需求。Collection接口中定义了以下方法:

boolean add(E e) //向集合中添加一个元素,若添加元素后集合发生了变化就返回true,若没有发生变化,就返回false。(optional operation).
boolean addAll(Collection<? extends E> c) //添加给定集合c中的所有元素到该集合中(optional operation).
void clear() //(optional operation).
boolean contains(Object o) //判断该集合中是否包含指定对象
boolean containsAll(Collection<?> c)
boolean equals(Object o)
int hashCode()
boolean isEmpty()
Iterator<E> iterator()
boolean remove(Object o) //移除给定对象的一个实例(有的具体集合类型允许重复元素) (optional operation).
boolean removeAll(Collection<?> c) //(optional operation).
boolean retainAll(Collection<?> c) //仅保留给定集合c中的元素(optional operation).
int size()
Object[] toArray()
<T> T[] toArray(T[] a)
로그인 후 복사

我们注意到有些方法后面注释中标注了“optional operation”,意思是Collection接口的实现类究竟需不需要实现这个方法视具体情况而定。比如有些具体的集合类型不允许向其中添加对象,那么它就无需实现add方法。我们可以看到,Collection对象必须实现的方法有:contains方法、containsAll方法、isEmpty方法、iterator方法、size方法、两个toArray方法以及equals方法、hashCode方法,其中最后两个方法继承自Object类。

我们来说一下两个toArray方法,它们的功能都是都是返回这个集合的对象数组。第二个方法接收一个arrayToFill参数,当这个参数数组足够大时,就把集合中的元素都填入这个数组(多余空间填null);当arrayToFill不够大时,就会创建一个大小与集合相同,类型与arrayToFill相同的数组,并填入集合元素。

Collection接口的直接子接口主要有三个:List接口、Set接口和Queue接口。下面我们对它们进行逐一介绍。

List接口

我们同样先看下它的官方定义:

An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.Unlike sets, lists typically allow duplicate elements. More formally, lists typically allow pairs of elements e1 and e2 such that e1.equals(e2), and they typically allow multiple null elements if they allow null elements at all.

大概意思是:List是一个有序的集合类型(也被称作序列)。使用List接口可以精确控制每个元素被插入的位置,并且可以通过元素在列表中的索引来访问它。列表允许重复的元素,并且在允许null元素的情况下也允许多个null元素。

我们再来看下它定义了哪些方法:

ListIterator<E> listIterator();
void add(int i, E element);
E remove(int i);
E get(int i);
E set(int i, E element);
int indexOf(Object element);
로그인 후 복사

我们可以看到,列表支持对指定位置元素的读写与移除。我们注意到,上面有一个listIterator方法,它返回一个列表迭代器。我们来看一看ListIterator接口都定义了哪些方法:

void add(E e) //在当前位置添加一个元素
boolean hasNext() //返回ture如果还有下个元素(在正向遍历列表时使用)
boolean hasPrevious() //反向遍历列表时使用
E next() //返回下一个元素并将cursor(也就是我们上文提到的”指针“)前移一个位置
int nextIndex() //返回下一次调用next方法将返回的元素的索引
E previous() //返回前一个元素并将cursor向前移动一个位置
int previousIndex() //返回下一次调用previous方法将返回的元素的索引void remove() //从列表中移除最近一次调用next方法或previous方法返回的元素
void set(E e) //用e替换最近依次调用next或previous方法返回的元素
로그인 후 복사

ListIterator是Iterator的子接口,它支持像双向迭代这样更加特殊化的操作。综合以上,我们可以看到,List接口支持两种访问元素的方式:使用列表迭代器顺序访问或者使用get/set方法随机访问。

Java类库中常见的实现了List接口的类有:ArrayList, LinkedList,Stack,Vector,AbstractList,AbstractSequentialList等等。

ArrayList

ArrayList是一个可动态调整大小的数组,允许null类型的元素。我们知道,Java中的数组大小在初始化时就必须确定下来,而且一旦确定就不能改变,这会使得在很多场景下不够灵活。ArrayList很好地帮我们解决了这个问题,当我们需要一个能根据包含元素的多少来动态调整大小的数组时,那么ArrayList正是我们所需要的。

我们先来看看这个类的常用方法:

boolean add(E e) //添加一个元素到数组末尾
void add(int index, E element) //添加一个元素到指定位置
void clear()
boolean contains(Object o)
void ensureCapacity(int minCapacity) //确保ArrayList至少能容纳参数指定数目的对象,若有需要会增加ArrayList实例的容量。
E get(int index) //返回指定位置的元素
int indexOf(Object o)
boolean isEmpty()
Iterator<E> iterator()
ListIterator<E> listIterator()
E remove(int index)
boolean remove(Object o)
E set(int index, E element)
int size()
로그인 후 복사

当我们插入了比较多的元素,导致ArrayList快要装满时,它会自动增长容量。ArrayList内部使用一个Object数组来存储元素,自动增长容量是通过创建一个新的容量更大的Object数组,并将元素从原Object数组复制到新Object数组来实现的。若要想避免这种开销,在知道大概会容纳多少数据时,我们可以在构造时指定好它的大小以尽量避免它自动增长的发生;我们也可以调用ensureCapacity方法来增加ArrayList对象的容量到我们指定的大小。ArrayList有以下三个构造器:

ArrayList()
ArrayList(Collection<? extends E> c)
ArrayList(int initialCapacity) //指定初始capacity,即内部Object数组的初始大小
로그인 후 복사
LinkedList类

LinkedList类代表了一个双向链表,允许null元素。这个类同ArrayList一样,不是线程安全的。
这个类中主要有以下的方法:

void addFirst(E element);
void addLast(E element);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
로그인 후 복사

这些方法的含义正如它们的名字所示。LinkedList作为List接口的实现类,自然包含了List接口中定义的add等方法。LinkedList的add方法实现有以下两种:

boolean add(E e) //把元素e添加到链表末尾
void add(int index, E element) //在指定索引处添加元素
로그인 후 복사

LinkedList的一个缺陷在于它不支持对元素的高效随机访问,要想随机访问其中的元素,需要逐个扫描直到遇到符合条件的元素。只有当我们需要减少在列表中间添加或删除元素操作的代价时,可以考虑使用LinkedList。

Set接口

Set接口与List接口的重要区别就是它不支持重复的元素,至多可以包含一个null类型元素。Set接口定义的是数学意义上的“集合”概念。
Set接口主要定义了以下方法:

boolean add(E e)
void clear()
boolean contains(Object o)
boolean isEmpty()
boolean equals(Object obj)
Iterator<E> iterator()
boolean remove(Object o)
boolean removeAll(Collection<?> c)
int size()
Object[] toArray()
<T> T[] toArray(T[] a)
로그인 후 복사

Set接口并没有显式要求其中的元素是有序或是无序的,它有一个叫做SortedSet的子接口,这个接口可以用来实现对Set元素的排序,SortedSet还有叫做NavigableSet的子接口,这个接口定义的方法可以在有序Set中进行查找和遍历。Java类库中实现了Set接口的类主要有:AbstractSet,HashSet,TreeSet,EnumSet,LinkedHashSet等等。其中,HashSet与TreeSet都是AbstractSet的子类。那么,为什么Java类库要提供AbstractSet这个抽象类呢?答案是为了让我们在自定义实现Set接口的类时不必“从零开始”,AbstractSet这个抽象类已经为我们实现了Set接口中的一些常规方法,而一些灵活性比较强的方法可以由我们自己来定义,我们只需要继承AbstractSet这个抽象类即可。类似的抽象类还有很多,比如我们上面提到的实现了List接口的AbstractList抽象类就是LinkedList和ArrayList的父类。Java官方文档中提到,HashSet和TreeSet分别基于HashMap和TreeMap实现(我们在后面会简单介绍HashMap和TreeMap),他们的区别在于Set接口是一个对象的集(数学意义上的”集合“),Map是一个键值对的集合。而且由于它们分别是对Set和Map接口的实现,相应添加与删除元素的方法也取决于具体接口的定义。

Queue接口

Queue接口是对队列这种数据结构的抽象。一般的队列实现允许我们高效的在队尾添加元素,在队列头部删除元素(First in, First out)。Queue接口还有一个名为Deque的子接口,它允许我们高效的在队头或队尾添加/删除元素,实现了Deque的接口的集合类即为双端队列的一种实现(比如LinkedList就实现了Deque接口)。Queue接口定义了以下方法:

boolean add(E e) //添加一个元素到队列中,若队列已满会抛出一个IllegalStateException异常
E element() //获取队头元素
boolean offer(E e) //添加一个元素到队列中,若队列已满返回false
E peek() //获取队头元素,若队列为空返回null
E poll() //返回并移除队头元素,若队列为空返回null
E remove() //返回并移除队头元素
로그인 후 복사

我们注意观察下上面的方法:add与offer,element与peek,remove与poll看似是三对儿功能相同的方法。它们之间的重要区别在于前者若操作失败会抛出一个异常,后者若操作失败会从返回值体现出来(比如返回false或null),我们可以根据具体需求调用它们中的前者或后者。

实现Queue接口的类主要有:AbstractQueue, ArrayDeque, LinkedList,PriorityQueue,DelayQueue等等。关于它们具体的介绍可参考官方文档或相关的文章。

Map接口

我们先来看下它的定义:

An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value.The Map
interface provides three collection views, which allow a map’s contents to be viewed as a set of keys, collection of values, or set of key-value mappings. The order of a map is defined as the order in which the iterators on the map’s collection views return their elements. Some map implementations, like the TreeMap
class, make specific guarantees as to their order; others, like the HashMap
class, do not.

大概意思是这样的:一个把键映射到值的对象被称作一个Map对象。映射表不能包含重复的键,每个键至多可以与一个值关联。Map接口提供了三个集合视图(关于集合视图的概念我们下面会提到):键的集合视图、值的集合视图以及键值对的集合视图。一个映射表的顺序取决于它的集合视图的迭代器返回元素的顺序。一些Map接口的具体实现(比如TreeMap)保证元素有一定的顺序,其它一些实现(比如HashMap)则不保证元素在其内部有序。

也就是说,Map接口定义了一个类似于“字典”的规范,让我们能够根据键快速检索到它所关联的值。我们先来看看Map接口定义了哪些方法:

void clear()
boolean containsKey(Object key) //判断是否包含指定键
boolean containsValue(Object value) //判断是否包含指定值
boolean isEmpty()
V get(Object key) //返回指定键映射的值
V put(K key, V value) //放入指定的键值对
V remove(Object key)
int size()
Set<Map.Entry<K,V>> entrySet() 
Set<K> keySet()
Collection<V> values()
로그인 후 복사

后三个方法在我们下面介绍集合视图时会具体讲解。

Map接口的具体实现类主要有:AbstractMap,EnumMap,HashMap,LinkedHashMap,TreeMap。HashTable。

HashMap

我们看一下HashMap的官方定义:

HashMap是基于哈希表这个数据结构的Map接口具体实现,允许null键和null值。这个类与HashTable近似等价,区别在于HashMap不是线程安全的并且允许null键和null值。由于基于哈希表实现,所以HashMap内部的元素是无序的。HashMap对与get与put操作的时间复杂度是常数级别的(在散列均匀的前提下)。对HashMap的集合视图进行迭代所需时间与HashMap的capacity(bucket的数量)加上HashMap的尺寸(键值对的数量)成正比。因此,若迭代操作的性能很重要,不要把初始capacity设的过高(不要把load factor设的过低)。

有两个因素会影响一个HashMap对象的性能:intial capacity(初始容量)和load factor(负载因子)。intial capacity就是HashMap对象刚创建时其内部的哈希表的“桶”的数量(请参考哈希表的定义)。load factor等于maxSize / capacity,也就是HashMap所允许的最大键值对数与桶数的比值。增大load factor可以节省空间但查找一个元素的时间会增加,减小load factor会占用更多的存储空间,但是get与put的操作会更快。当HashMap中的键值对数量超过了maxSize(即load factor与capacity的乘积),它会再散列,再散列会重建内部数据结构,桶数(capacity)大约会增加到原来的两倍。

HashMap默认的load factor大小为0.75,这个数值在时间与空间上做了很好的权衡。当我们清楚自己将要大概存放多少数据时,也可以自定义load factor的大小。

HashMap的构造器如下:

HashMap()
HashMap(int initialCapacity)
HashMap(int initialCapacity, float loadFactor)
HashMap(Map<? extends K,? extends V> m) //创建一个新的HashMap,用m的数据填充
로그인 후 복사

常用方法如下:

void clear()
boolean containsKey(Object key)
boolean containsValue(Object value)
V get(Object key)
V put(K key, V value)
boolean isEmpty()
V remove(Object key)
int size()
Collection<V> values()
Set<Map.Entry<K,V>> entrySet()
Set<K> keySet()
로그인 후 복사

它们的功能都很直观,更多的使用细节可以参考Java官方文档,这里就不贴上来了。这里简单地提一下WeakHashMap,它与HashMap的区别在于,存储在其中的key是“弱引用”的,也就是说,当不再存在对WeakHashMap中的键的外部引用时,相应的键值对就会被回收。关于WeakHashMap和其他类的具体使用方法及注意事项,大家可以参考官方文档。下面我们来简单地介绍下另一个Map接口的具体实现——TreeMap。

TreeMap

它的官方定义是这样的:

TreeMap一个基于红黑树的Map接口实现。TreeMap中的元素的有序的,排序的依据是存储在其中的键的natural ordering(自然序,也就是数字从小到大,字母的话按照字典序)或者根据在创建TreeMap时提供的Comparator对象,这取决于使用了哪个构造器。TreeMap的containsKey, get, put和remove操作的时间复杂度均为log(n)。

TreeMap有以下构造器:

TreeMap() //使用自然序对其元素进行排序
TreeMap(Comparator<? super K> comparator) //使用一个比较器对其元素进行排序
TreeMap(Map<? extends K,? extends V> m) //构造一个与映射表m含有相同元素的TreeMap,用自然序进行排列
TreeMap(SortedMap<K,? extends V> m) //构造一个与有序映射表m含有相同元素及元素顺序的TreeMap
로그인 후 복사

它的常见方法如下:

Map.Entry<K,V> ceilingEntry(K key) //返回一个最接近且大于等于指定key的键值对。
K ceilingKey(K key)
void clear()
Comparator<? super K> comparator() //返回使用的比较器,若按自然序则返回null
boolean containsKey(Object key)
boolean containsValue(Object value)
NavigableSet<K> descendingKeySet() //返回一个包含在TreeMap中的键的逆序的NavigableSet视图
NavigableMap<K,V> descendingMap()
Set<Map.Entry<K,V>> entrySet()
Map.Entry<K,V> firstEntry() //返回键最小的键值对
Map.Entry<K,V> floorEntry(K key) //返回一个最接近指定key且小于等于它的键对应的键值对
K floorKey(K key)
V get(Object key)
Set<K> keySet()
Map.Entry<K,V> lastEntry() //返回与最大的键相关联的键值对
K lastKey()
로그인 후 복사

建议大家先了解下红黑树这个数据结构的原理及实现(可参考算法(第4版) (豆瓣)),然后再去看官方文档中关于这个类的介绍,这样学起来会事半功倍。

最后再简单地介绍下NavigableMap这个接口:

实现了这个接口的类支持一些navigation methods,比如lowerEntry(返回小于指定键的最大键所关联的键值对),floorEntry(返回小于等于指定键的最大键所关联的键值对),ceilingEntry(返回大于等于指定键的最小键所关联的键值对)和higerEntry(返回大于指定键的最小键所关联的键值对)。一个NavigableMap支持对其中存储的键按键的递增顺序或递减顺序的遍历或访问。NavigableMap接口还定义了firstEntry、pollFirstEntry、lastEntry和pollLastEntry等方法,以准确获取指定位置的键值对。

总的来说,NavigableMap接口正如它的名字所示,支持我们在映射表中”自由的航行“,正向或者反向迭代其中的元素并获取我们需要的指定位置的元素。TreeMap实现了这个接口。

视图(View)与包装器

下面我们来解决一个上面遗留的问题,也就是介绍一下集合视图的概念。Java中的集合视图是用来查看集合中全部或部分数据的一个”窗口“,只不过通过视图我们不仅能查看相应集合中的元素,对视图的操作还可能会影响到相应的集合。通过使用视图可以获得其他的实现了Map接口或Collection接口的对象。比如我们上面提到的TreeMap和HashMap的keySet()方法就会返回一个相应映射表对象的视图。也就是说,keySet方法返回的视图是一个实现了Set接口的对象,这个对象中又包含了一系列键对象。

轻量级包装器

Arrays.asList会发挥一个包装了Java数组的集合视图(实现了List接口)。请看以下代码:

public static void main(String[] args) {
  String[] strings = {"first", "second", "third"};
  List<String> stringList = Arrays.asList(strings);
  String s1 = stringList.get(0);
  System.out.println(s1);
  stringList.add(0, "new first");
}
로그인 후 복사

以上代码会编译成功,但是在运行时会抛出一个UnsupportedOperationException异常,原因是调用了改变列表大小的add方法。Arrays.asList方法返回的封装了底层数组的集合视图不支持对改变数组大小的方法(如add方法和remove方法)的调用(但是可以改变数组中的元素)。实际上,这个方法调用了以下方法:

Collections.nCopies(n, anObject);
로그인 후 복사

这个方法会返回一个实现了List接口的不可修改的对象。这个对象包含了n个元素(anObject)。

子范围

我们可以为很多集合类型建立一个称为子范围(subrange)的集合视图。例如以下代码抽出group中的第10到19个元素(从0开始计数)组成一个子范围:

List subgroup = group.subList(10, 20); //group为一个实现了List接口的列表类型
로그인 후 복사

List接口所定义的操作都可以应用于子范围,包括那些会改变列表大小的方法,比如以下方法会把subgroup列表清空,同时group中相应的元素也会从列表中移除:

subgroup.clear();
로그인 후 복사

对于实现了SortedSet接口的有序集或是实现了SortedMap接口的有序映射表,我们也可以为他们创建子范围。SortedSet接口定义了以下三个方法:

SortedSet<E> subSet(E from, E to); 
SortedSet<E> headSet(E to);
SortedSet<E> tailSet(E from);
로그인 후 복사

SortedMap也定义了类似的方法:

SortedMap<K, V> subMap(K from, K to);
SortedMap<K, V> headMap(K to);
SortedMap<K, V> tailMap(K from);
로그인 후 복사

不可修改的视图

Collections类中的一些方法可以返回不可修改视图(unmodifiable views):

Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableSortedSet
Collections.unmodifiableMap
Collections.unmodifiableSortedMap
로그인 후 복사

同步视图

若集合可能被多个线程并发访问,那么我们就需要确保集合中的数据不会被破坏。Java类库的设计者使用视图机制来确保常规集合的线程安全。比如,我们可以调用以下方法将任意一个实现了Map接口的集合变为线程安全的:

Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
로그인 후 복사

被检验视图

我们先看一下这段代码:

ArrayList<String> strings = new ArrayList<String>();
ArrayList rawList = strings;
rawList.add(new Date());
로그인 후 복사

在以上代码的第二行,我们把泛型数组赋值给了一个原始类型数组,这通常只会产生一个警告。而第三行我们往rawList中添加一个Date对象时,并不会产生任何错误。因为rawList内部存储的实际上是Object对象,而任何对象都可以转换为Object对象。那么我们怎么避免这一问题呢,请看以下代码:

ArrayList<String> strings = new ArrayList<String>();
List<String> safeStrings = Collections.checkedList(strings, String.class);
ArrayList rawList = safeStrings;
rawList.add(new Date()); //Checked list throws a ClassCastException
로그인 후 복사

在上面,我们通过包装strings得到一个被检验视图safeStrings。这样在尝试添加非String对象时,便会抛出一个ClassCastException异常。

集合视图的本质

集合视图本身不包含任何数据,它只是对相应接口的包装。集合视图所支持的所有操作都是通过访问它所关联的集合类实例来实现的。我们来看看HashMap的keySet方法的源码:

public Set<K> keySet() {
  Set<K> ks;
  return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
} 

final class KeySet extends AbstractSet<K> {
  public final int size() { 
    return size; 
  }
  public final void clear() { 
    HashMap.this.clear(); 
  }
  public final Iterator<K> iterator() { 
    return new KeyIterator(); 
  }
  public final boolean contains(Object o) { 
    return containsKey(o); 
  }
  public final boolean remove(Object key) {
    return removeNode(hash(key), key, null, false, true) != null;
  }
  public final Spliterator<K> spliterator() {
    return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
  }
  public final void forEach(Consumer<? super K> action) {
    Node<K,V>[] tab;
    if (action == null) throw new NullPointerException();
    if (size > 0 && (tab = table) != null) {
      int mc = modCount;
      for (int i = 0; i < tab.length; ++i) {
        for (Node<K,V> e = tab[i]; e != null; e = e.next)
          action.accept(e.key);
        }
        if (modCount != mc) throw new ConcurrentModificationException();
      }
  }
}
로그인 후 복사

我们可以看到,实际上keySet()方法返回一个内部final类KeySet的实例。我们可以看到KeySet类本身没有任何实例变量。我们再看KeySet类定义的size()实例方法,它的实现就是通过直接返回HashMap的实例变量size。还有clear方法,实际上调用的就是HashMap对象的clear方法。

keySet方法能够让你直接访问到Map的键集,而不需要复制数据或者创建一个新的数据结构,这样做往往比复制数据到一个新的数据结构更加高效。考虑这样一个场景:你需要把一个之前创建的数组传递给一个接收List参数的方法,那么你可以使用Arrays.asList方法返回一个包装了数组的视图(这需要的空间复杂度是常数级别的),而不用创建一个新的ArrayList再把原数组中的数据复制过去。

Collections类

我们要注意到Collections类与Collection接口的区别:Collection是一个接口,而Collections是一个类(可以看做一个静态方法库)。下面我们看一下官方文档对Collections的描述:

Collections类包含了大量用于操作或返回集合的静态方法。它包含操作集合的多态算法,还有包装集合的包装器方法等等。这个类中的所有方法在集合或类对象为空时均会抛出一个NullPointerException。

Collections 클래스의 일반적인 메소드에 대해서는 위에서 이미 몇 가지 소개를 했습니다. 더 자세한 소개는 Java 공식 문서를 참조하세요.

요약

Java 컬렉션 프레임워크와 관련하여 먼저 몇 가지 핵심 인터페이스를 파악해야 합니다. 아래 그림을 참조하세요(아래 그림에서는 LinkList의 철자가 잘못되었으므로 LinkedList여야 합니다).

또한 이러한 인터페이스가 어떤 종류의 메커니즘을 설명하는지 이해하고 이를 출발점으로 사용하여 어떤 클래스가 어떤 메커니즘을 구현하는지 이해해야 합니다. 이와 같은 하향식 학습을 통해 공통 컬렉션 클래스의 사용법을 빠르게 익힐 수 있습니다. 자주 사용하는 일부 클래스의 경우 소스 코드를 읽고 구현 세부 사항을 이해할 수 있으므로 나중에 더 쉽게 사용할 수 있습니다. 그러나 일부 컬렉션 클래스(예: TreeMap 및 HashMap)의 소스 코드를 읽으려면 데이터 구조 및 알고리즘에 대한 특정 기본 지식이 필요합니다. 이러한 점에서 Algorithms(4th Edition)(Douban)을 읽는 것이 좋습니다.


위 내용은 Java의 핵심 기술 포인트인 컬렉션 프레임워크에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿