Maison > Java > javaDidacticiel > Une introduction détaillée aux points techniques fondamentaux de Java : le cadre de collection

Une introduction détaillée aux points techniques fondamentaux de Java : le cadre de collection

黄舟
Libérer: 2017-03-22 10:55:15
original
1607 Les gens l'ont consulté

Présentation

Le framework de collection Java se compose d'une série d'interfaces, de classes abstraites et de classes d'implémentation concrètes de la bibliothèque de classes Java. La collection dont nous parlons ici consiste à organiser un groupe d'objets ensemble puis à manipuler les données en fonction de différents besoins. Un type de collection est un conteneur qui contient ces objets. En d’autres termes, la fonctionnalité de collecte la plus élémentaire consiste à rassembler un groupe d’objets pour une gestion centralisée. Les types de collection peuvent être subdivisés en de nombreux sous-types différents en fonction de critères tels que si les objets en double sont autorisés dans la collection et si les objets sont organisés dans un certain ordre.

Le framework de collection Java nous fournit un ensemble de mécanismes de base et d'implémentations de référence de ces mécanismes L'interface de collection de base est l'interface Collection, et d'autres interfaces associées incluent l'interface Iterator et la. Interface RandomAccess. Les interfaces de ces cadres de collection définissent le mécanisme de base qu'un type de collection doit implémenter.La bibliothèque de classes Java nous fournit quelques implémentations de référence de types de collection spécifiques.Selon les différentes exigences d'organisation et d'utilisation des données, différentes interfaces doivent simplement être implémentées. . La bibliothèque de classes Java nous fournit également des classes abstraites qui fournissent une implémentation partielle des fonctions de type collection. Nous pouvons également implémenter davantage nos propres types de collection sur cette base.

Interface de collection

Itérateur

Regardons d'abord la définition de cette interface :

public interface Collection<E> extends Iterable<E>
Copier après la connexion

Tout d'abord, elle utilise un paramètre de type ; Deuxièmement, il implémente l'interface Iterable. Regardons la définition de l'interface Iterable :

public interface Iterable<T> {
  Iterator<T> iterator();
}
Copier après la connexion

Nous pouvons voir que cette interface ne définit qu'une seule méthode, ce qui nous oblige à le faire. return a Implémente un objet de type Iterator, jetons donc un œil à la définition de Iterator :

public interface Iterator<E> { 
  boolean hasNext(); 
  E next(); 
  void remove();
}
Copier après la connexion

En parlant de cela, parlons brièvement de Iterator (Iterator) Ce truc. Ci-dessus, nous avons mentionné un total de deux interfaces liées aux itérateurs : l'interface Iterable et l'interface Iterator Au sens littéral, la première signifie "itérable" et la seconde signifie "itérateur". de cette façon : la classe qui implémente l'interface Iterable est un iterable ; la classe qui implémente l'interface Iterator est un iterator

An. L'itérateur est quelque chose que nous utilisons pour parcourir les objets d'une collection.C'est-à-dire que pour une collection, nous n'accédons pas directement aux éléments à la position correspondante via l'index du tableau comme un tableau de type primitif. c'est que le comportement de traversée du type collection est séparé de l'objet collection traversé, de sorte que nous n'avons pas besoin de nous soucier de l'implémentation spécifique du type collection, tant que nous obtenons l'itérateur de l'objet collection, vous. peut parcourir les objets de cette collection. Les détails tels que l'ordre de parcours des objets sont tous gérés par son itérateur. Maintenant, trions les choses mentionnées précédemment : Tout d'abord, l'interface Collection implémente l'interface Iterable nous oblige à implémenter la méthode iterator, cette méthode renvoie un objet itérateur. Un objet itérateur est un objet qui implémente l'interface Iterator. Cette interface nous oblige à implémenter les trois méthodes hasNext(), next() et remove(). si l'objet a été traversé). La méthode suivante renverra l'élément suivant (s'il n'y a pas d'élément suivant, son appel entraînera la levée d'une exception NoSuchElementException. La méthode Remove est utilisée pour supprimer l'élément le plus récent). renvoyé en appelant la méthode next (si la méthode remove est appelée directement sans appeler la méthode next, une erreur sera signalée. On peut imaginer qu'avant de commencer à itérer la collection, il y a un pointeur pointant vers le devant du premier élément). de la collection, et la méthode suivante est appelée pour la première fois. Ensuite, ce pointeur "balayera" le premier élément et le renverra. Appeler la méthode hasNext consiste à voir s'il y a des éléments derrière ce pointeur. , ce pointeur pointe toujours vers l'élément qui vient d'être parcouru et le prochain élément à parcourir. Habituellement, le code pour itérer un objet de collection ressemble à ceci :

Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while (iter.hasNext()) {
  String element = iter.next();
  //do something with element
}
Copier après la connexion

À partir de Java SE 5.0, nous pouvons utiliser un. version équivalente mais plus concise de l'extrait de code ci-dessus :

for (String element : c) {
  //do something with element
}
Copier après la connexion

Nous avons mentionné ci-dessus que la méthode Remove de l'interface Iterator doit être appelée après que la méthode suivante ait renvoyé un élément. Cela est vrai pour les classes fournies par. la bibliothèque de classes Java qui implémente l'interface Collection. Bien entendu, nous pouvons modifier ce comportement par défaut en définissant une classe de collection qui implémente l'interface Collection (sauf s'il y a une bonne raison, il est préférable de ne pas le faire).

Interface de collection

Regardons d'abord sa définition officielle :

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)
Copier après la connexion

我们注意到有些方法后面注释中标注了“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);
Copier après la connexion

我们可以看到,列表支持对指定位置元素的读写与移除。我们注意到,上面有一个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方法返回的元素
Copier après la connexion

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()
Copier après la connexion

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

ArrayList()
ArrayList(Collection<? extends E> c)
ArrayList(int initialCapacity) //指定初始capacity,即内部Object数组的初始大小
Copier après la connexion
LinkedList类

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

void addFirst(E element);
void addLast(E element);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
Copier après la connexion

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

boolean add(E e) //把元素e添加到链表末尾
void add(int index, E element) //在指定索引处添加元素
Copier après la connexion

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)
Copier après la connexion

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() //返回并移除队头元素
Copier après la connexion

我们注意观察下上面的方法: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()
Copier après la connexion

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

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的数据填充
Copier après la connexion

常用方法如下:

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()
Copier après la connexion

它们的功能都很直观,更多的使用细节可以参考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
Copier après la connexion

它的常见方法如下:

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()
Copier après la connexion

建议大家先了解下红黑树这个数据结构的原理及实现(可参考算法(第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");
}
Copier après la connexion

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

Collections.nCopies(n, anObject);
Copier après la connexion

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

子范围

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

List subgroup = group.subList(10, 20); //group为一个实现了List接口的列表类型
Copier après la connexion

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

subgroup.clear();
Copier après la connexion

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

SortedSet<E> subSet(E from, E to); 
SortedSet<E> headSet(E to);
SortedSet<E> tailSet(E from);
Copier après la connexion

SortedMap也定义了类似的方法:

SortedMap<K, V> subMap(K from, K to);
SortedMap<K, V> headMap(K to);
SortedMap<K, V> tailMap(K from);
Copier après la connexion

不可修改的视图

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

Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableSortedSet
Collections.unmodifiableMap
Collections.unmodifiableSortedMap
Copier après la connexion

同步视图

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

Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
Copier après la connexion

被检验视图

我们先看一下这段代码:

ArrayList<String> strings = new ArrayList<String>();
ArrayList rawList = strings;
rawList.add(new Date());
Copier après la connexion

在以上代码的第二行,我们把泛型数组赋值给了一个原始类型数组,这通常只会产生一个警告。而第三行我们往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
Copier après la connexion

在上面,我们通过包装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();
      }
  }
}
Copier après la connexion

我们可以看到,实际上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。

Concernant les méthodes courantes dans la classe Collections, nous avons déjà fait quelques introductions ci-dessus Pour une introduction plus détaillée, vous pouvez vous référer à la documentation officielle Java.

Résumé

Concernant le framework de collection Java, nous devons d'abord comprendre quelques interfaces de base, veuillez voir l'image ci-dessous (LinkList est mal orthographié dans l'image ci-dessous, il devrait s'agir de LinkedList) :

Nous devons également comprendre quel type de mécanismes ces interfaces décrivent, puis utiliser cela comme point de départ pour comprendre quelles classes implémentent quels mécanismes. Avec un apprentissage descendant comme celui-ci, nous pouvons rapidement maîtriser l’utilisation des classes de collection courantes. Pour certaines classes que nous utilisons souvent, nous pouvons également lire son code source et comprendre les détails de son implémentation, afin de pouvoir l'utiliser plus facilement à l'avenir. Cependant, la lecture du code source de certaines classes de collection (telles que TreeMap et HashMap) nécessite certaines connaissances de base en matière de structures de données et d'algorithmes. À cet égard, il est recommandé de lire Algorithmes (4e édition) (Douban).


Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal