Java 集合框架中的 Collection 接口
一、介绍
集合继承实现层次结构中的根接口。集合表示一组对象,称其为元素。一些集合允许重复元素,而另一些不允许。一些是有序的,而另一些是无序的。
具有确定的遍历顺序的集合通常是 SequencedCollection
接口的子类型。JDK 没有直接实现 SequencedCollection
接口的类,它提供了更具体的子接口实现,如 Set
和 List
。此接口通常用于在需要最大通用性时传递和操作集合。
所有通用的集合实现类(通常通过其子接口间接实现集合)应提供两个“标准”构造函数:
- 无参构造函数,用于创建空集合
- 带有类型为
Collection
的单参数构造函数,用于创建包含与其参数相同元素的新集合。
实际上,第二个构造函数允许用户复制任何集合,生成所需实现类型的等效集合。虽然接口不能包含构造函数,无法强制执行此约定,但 Java 平台库中的所有通用集合实现都遵守该约定。
某些方法被指定为可选操作。如果某个集合实现不支持某个操作,它应将相应的方法定义为抛出 UnsupportedOperationException
。这些方法在集合接口的方法规范中被标记为“可选操作”。
某些集合的实现对其中的元素有限制。例如,一些实现禁止 null
元素,一些对元素的类型有限制。尝试添加不符合条件的元素会抛出未经检查的异常,通常是 NullPointerException
或 ClassCastException
。尝试查询不符合条件的元素是否存在可能抛出异常,或可能简单地返回 false
;一些实现会表现为前者,另一些表现为后者。更广泛地说,尝试对不符合条件的元素进行操作,其完成不会导致该元素插入集合中,可能抛出异常或成功,这取决于实现。这些异常在此接口的规范中被标记为“可选”。
每个集合自行决定其同步策略。如果集合的实现不保证操作时同步的,则当前线程调用一个由其他线程正在修改的集合的任何方法可能会导致不可预料的行为。调用包括直接调用,将集合传递给可能调用该集合的方法,以及使用现有迭代器检查集合。
集合框架接口中的许多方法是基于 equals
方法的。例如,contains(Object o)
方法的说明:“当且仅当此集合包含至少一个元素 e
,且满足 (o==null ? e==null : o.equals(e))
时,返回 true
。”
此规范不应被解读为在对非 null
参数 o
调用 Collection.contains
时,会导致对任何元素 e
调用 o.equals(e)
。实现可以自由地优化,避免调用 equals
,例如,先比较两个元素的哈希码。Object.hashCode()
规范保证了哈希码不相等的两个对象不能相等。更广泛地说,各种集合框架接口的实现可以根据需要利用底层 Object
方法的指定行为。
一些执行集合递归遍历的集合操作可能会在自引用实例(集合直接或间接包含自身)中因异常而失败。这包括 clone()
、equals()
、hashCode()
和 toString()
方法。该接口的实现可以选择处理自引用场景,但大多数的实现并未这样做。
1、视图集合
大多数集合在管理它们所包含的元素时,都会管理其元素的存储。相比之下,视图集合本身不存储元素,而是依赖于其基础集合来存储实际元素。未由视图集合本身处理的操作会委托给基础集合。
视图集合的示例包括由方法如 Collections.checkedCollection
、Collections.synchronizedCollection
和 Collections.unmodifiableCollection
返回的包装集合。还包括提供相同元素不同表示的集合,例如通过 List.subList
、NavigableSet.subSet
、Map.entrySet
或 SequencedCollection.reversed
提供的集合。
对基础集合所做的任何更改在视图集合中都是可见的。相应地,对视图集合所做的任何更改(如果允许更改)也会写入基础集合。虽然从技术上讲它们不是集合,但 Iterator
和 ListIterator
的实例也可以允许将修改写入基础集合,在某些情况下,对基础集合的修改在迭代期间对 Iterator
是可见的。
2、不可修改集合
此接口的某些方法被视为“破坏性”方法,并被称为“变异”方法,因为它们会修改它们操作的集合中包含的对象组。如果集合实现不支持某个操作,这些方法可以被指定为抛出 UnsupportedOperationException
。如果调用对集合没有影响,这些方法应(但不强制要求)抛出 UnsupportedOperationException
。例如,考虑一个不支持 add
操作的集合。如果在这个集合上调用 addAll
方法,并传入一个空集合作为参数,会发生什么?添加零个元素不会产生影响,因此此集合可以不执行任何操作,也不抛出异常。然而,建议在这种情况下无条件地抛出异常,因为只在某些情况下抛出异常可能导致编程错误。
不可修改的集合是指其所有变异器方法(如上所定义)都被指定为抛出 UnsupportedOperationException
的集合。因此,调用任何方法都不能修改这样的集合。为了使一个集合真正不可修改,从该集合派生的任何视图集合也必须不可修改。例如,如果一个 List
是不可修改的,那么由 List.subList
返回的 List
也应该是不可修改的。
不可修改的集合并不一定是不可变的。如果集合中的元素是可变的,即使集合本身不可修改,整个集合仍然是可变的。例如,考虑两个包含可变元素的不可修改列表。如果这些元素已被更改,多次调用 list1.equals(list2)
的结果可能不同,尽管两个列表都是不可修改的。然而,如果一个不可修改的集合包含的所有元素都是不可变的,那么它可以被认为是“有效不可变”的。
这里要注意不可修改集合和不可变集合的区分,在 Java 源码中可以看到不可修改(Unmodifiable)和不可变(Immutable)两个单词。
比如不可修改集合可以调用集合的 set 方法,并且会成功,不会对集合结构进行修改,但是可以替换集合内部的元素。而不可变集合在调用 set 方法时会抛出异常。
3、不可修改的视图集合
不可修改的视图集合是一个既不可修改又是视图的集合。它的变异方法(如上所述)会抛出 UnsupportedOperationException
,而读取和查询方法则委托给基础集合。其作用是为基础集合提供只读访问权限。这在向用户提供对内部集合的读权限,同时防止用户意外修改该集合时非常有用。不可修改的视图集合的示例包括通过 Collections.unmodifiableCollection
、Collections.unmodifiableList
等相关方法返回的集合。
注意,对基础集合的更改仍然是可能的,且如果发生更改,它们会通过不可修改的视图反映出来。因此,不可修改的视图集合不一定是不可变的。然而,如果不可修改视图的基础集合实际上是不可变的,或者对基础集合的唯一引用是通过不可修改的视图,那么该视图可以被视为实际上是不可变的。
4、集合的可序列化性
集合的可序列化性是可选的。因此,集合接口并没有声明实现 java.io.Serializable
接口。然而,可序列化性通常被认为是有用的,因此大多数集合实现都是可序列化的。
如果集合实现是公共类(如 ArrayList
或 HashMap
),并且它们确实是可序列化的,那么它们会声明实现 Serializable
接口。一些集合实现并不是公共类,例如不可修改的集合。在这些情况下,此类集合的可序列化性会在创建它们的方法规范中,或在其他合适的地方进行描述。如果集合的可序列化性没有被指定,则不能保证该集合是可序列化的。特别是,许多视图集合即使原始集合是可序列化的,也可能不是可序列化的。
实现了 Serializable
接口的集合实现并不能保证其一定是可序列化的。原因在于,集合通常包含其他类型的元素,且无法静态确定某些元素类型的实例是否实际上是可序列化的。例如,考虑一个可序列化的 Collection<E>
,其中 E
没有实现 Serializable
接口。如果集合只包含某个 E
的可序列化子类型的元素,或者是空的,那么该集合可能是可序列化的。因此,集合被称为条件可序列化的,因为集合整体的可序列化性取决于集合本身是否可序列化以及其中所有元素是否也可序列化。
另外一种情况出现在 SortedSet
和 SortedMap
的实例中。这些集合可以使用一个 Comparator
来对集合元素或映射键进行排序。这样的集合只有在所提供的 Comparator
也是可序列化时才是可序列化的。
二、源码
1 | public interface Collection<E> extends Iterable<E> { |
Collection 接口中的方法,按照操作类型分类,可以分为:
- 查询操作
- 修改操作
- 批量操作
- 比较和哈希
- default 方法
1、查询操作
1.1 size 方法
int size();
返回此集合中的元素数量。如果此集合包含的元素数量超过 Integer.MAX_VALUE
,则返回 Integer.MAX_VALUE
。
1.2 isEmpty 方法
boolean isEmpty();
如果集合不包含元素则返回 true
1.3 contains 方法
boolean contains(Object o);
如果此集合包含指定的元素,则返回 true。
当且仅当此集合包含至少一个元素 e
使得 Objects.equals(o, e)
,才返回 true。
1.4 iterator 方法
Iterator<E> iterator();
返回此集合中元素的迭代器。对于元素返回的顺序没有任何保证(除非此集合是提供保证的某个类的实例)。
1.5 toArray 方法
1.5.1 返回 Object 类型的数组
Object[] toArray();
返回一个包含此集合中所有元素的数组。如果此集合对其迭代器返回元素的顺序有保证,则此方法必须以相同的顺序返回元素。返回的数组的运行时类型为 Object
。
返回的数组将是“安全的”,因为此集合不会维护对它的任何引用。(换句话说,即使此集合是以一个数组为基础的,此方法也必须分配一个新数组)。因此,调用者可以自由修改返回的数组。
此方法充当基于数组和基于集合的 API 之间的桥梁。它返回一个运行时类型为 Object[]
的数组。使用 toArray(T[])
可以重用现有数组,或使用 toArray(IntFunction)
来控制数组的运行时类型。
1.5.2 返回指定类型的数组
<T> T[] toArray(T[] a);
返回一个包含此集合中所有元素的数组;返回数组的运行时类型为指定数组的类型。如果集合适合指定数组,则将其返回到该数组中。否则,将分配一个新数组,其运行时类型为指定数组的类型,并且大小与此集合相同。
如果此集合适合指定数组并且还有多余空间(即,数组的元素数量超过此集合),则数组中紧接着集合末尾的元素将被设置为 null
。(这在确定此集合的长度时很有用,仅当调用者知道此集合不包含任何 null
元素时)。
如果此集合对其迭代器返回元素的顺序有任何保证,则此方法必须以相同的顺序返回元素。
此方法充当基于数组和基于集合的 API 之间的桥梁。它允许在某些情况下重用现有数组。使用 toArray()
可以创建一个运行时类型为 Object[]
的数组,或者使用 toArray(IntFunction)
来控制数组的运行时类型。
假设 x
是一个已知仅包含字符串的集合。可以使用以下代码将集合导入一个先前分配的字符串数组中:
1 | String[] y = new String[SIZE]; |
返回值被重新赋值给变量 y
,因为如果集合 x
的元素太多,无法放入现有数组 y
中,则会分配并返回一个新数组。
注意:toArray(new Object[0])
的功能与 toArray()
是相同的。
2、修改操作
2.1 add 方法
boolean add(E e);
确保此集合包含指定元素(可选操作)。如果此集合因调用而发生变化,则返回 true
。如果此集合不允许重复并且已经包含指定元素,则返回 false
。
支持此操作的集合可能对可以添加到集合中的元素施加限制。特别是,某些集合将拒绝添加 null
元素,而其他集合则会对可以添加的元素类型施加限制。集合类应在其文档中清楚地说明对可以添加元素的任何限制。
如果集合因任何原因拒绝添加特定元素(除了已经包含该元素之外),则必须抛出异常(而不是返回 false
)。这保留了一个不变量,即在此调用返回后,集合始终包含指定元素。
2.2 remove 方法
boolean remove(Object o);
从此集合中移除指定元素的单个实例(如果存在的话)(可选操作)。
如果此集合包含一个或多个这样的元素,则移除一个元素 e
使得 Objects.equals(o, e)
为 true
。如果此集合包含指定元素(或者等价地说,如果此集合因调用而发生变化),则返回 true
。
3、批量操作
3.1 containsAll 方法
boolean containsAll(Collection<?> c);
如果此集合中包含指定集合中的所有元素,则返回 true
3.2 addAll 方法
boolean addAll(Collection<? extends E> c);
将指定集合中的所有元素添加到此集合中(可选操作)。如果在进行该操作时修改了指定集合,则该操作的行为是未定义的。(这意味着,如果指定集合是此集合,并且此集合非空,则该调用的行为是不可预料的。)如果指定集合有明确的遍历顺序,则其元素的处理通常会按照该顺序进行。
3.3 removeAll 方法
boolean removeAll(Collection<?> c);
移除此集合中所有也包含在指定集合中的元素(可选操作)。在此调用返回后,此集合将不与指定集合有任何共同元素。
3.4 retainAll 方法
boolean retainAll(Collection<?> c);
仅保留此集合中包含在指定集合中的元素(可选操作)。换句话说,从此集合中移除所有不包含在指定集合中的元素。
3.5 clear 方法
void clear();
从此集合中移除所有元素(可选操作)。此方法返回后,集合将为空。
4、比较和哈希
4.1 equals 方法
将指定的对象与此集合进行等同性比较。
尽管 Collection
接口没有对 Object.equals
添加任何规定,但直接实现 Collection
接口(换句话说,创建一个是 Collection
但不是 Set
或 List
的类)的程序员在选择覆盖 Object.equals
时必须格外小心。这并非必须操作,最简单的做法是依赖 Object
的实现,但实现者可能希望实现“值比较”以替代默认的“引用比较”。(List
和 Set
接口要求此类值比较。)
Object.equals
方法的一般约定指出,equals
必须是对称的(换句话说,仅当 b.equals(a)
为 true
时,a.equals(b)
才为 true
)。List.equals
和 Set.equals
的约定指出,列表仅与其他列表相等,集合仅与其他集合相等。因此,对于既不实现 List
也不实现 Set
接口的集合类,其自定义的 equals
方法在此集合与任何列表或集合进行比较时,必须返回 false
。(同样的逻辑,也不可能编写一个同时正确实现Set
和List
接口的类。)
4.2 hashCode 方法
返回此集合的哈希码值。虽然 Collection
接口没有对 Object.hashCode
添加任何规定,但程序员应该注意,任何重写了 Object.equals
方法的类也必须重写 Object.hashCode
方法,以满足 Object.hashCode
方法的一般约定。特别是,如果 c1.equals(c2)
为 true
,则必须有 c1.hashCode()==c2.hashCode()
。
5、默认方法
5.1 spliterator 方法
1 |
|
相关链接
OB links
OB tags
#Java