方法总览
Collection
接口是 Java 集合框架 Collection 部分的根接口,在官方注释中,将 Collection 定义为 a group of objects,并将其中包含的对象称为 element。该接口声明的方法如下所示:
public interface Collection<E> extends Iterable<E> {
/* Query Operations 查询操作 */
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator(); // ↑ Iterable
Object[] toArray();
<T> T[] toArray(T[] a);
default <T> T[] toArray(IntFunction<T[]> generator) {...} // since 11
/* Modification Operations 改动操作 */
boolean add(E e);
boolean remove(Object o);
/* Bulk Operations 批量操作 */
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
default boolean removeIf(Predicate<? super E> filter) {...}
boolean retainAll(Collection<?> c);
void clear();
/* Comparison and hashing 比较和哈希 */
boolean equals(Object o); // ↑ Object
int hashCode(); // ↑ Object
default Spliterator<E> spliterator() {...} // Iterable
default Stream<E> stream() {...}
default Stream<E> parallelStream() {...}
}
这里列出方法仅是为了让你有一个直观的印象,需要掌握的知识点已经在下面罗列好了。
two standard constructors
在官方注释中提到,该接口的所有实现类都应该提供 2 个构造方法:一个是无参构造方法,用于创建一个空的 Collection;一个入参为 Collection
类型的构造方法,用于创建一个拥有与其入参数相同元素的新 Collection。(这里直接使用 Collection 而非“集合”,是因为 Java “集合”约定俗成等于 Collection + Map,而 Collection 单词本身又翻译为“集合”)。
注意上述规定并不是强制性的,而是建议性的,但 Java 官方定义的 Collection
实现类都提供了这两种类型的构造方法。
3 个 toArray() 方法
Collection
接口声明了 3 个 toArray()
方法,分别是:
Object[] toArray(); // 返回类型为 Object 数组
<T> T[] toArray(T[] a); // 推荐使用
default <T> T[] toArray(IntFunction<T[]> generator) {...} // since 11
对于 Object[] toArray()
方法,你要特别注意该方法返回的是 Object
类型数组,而数组是不能强制类型转换的,所以不推荐使用该方法(阿里巴巴 Java 开发手册强制禁止使用该方法,并要求使用第二个方法)。
对于 <T> T[] toArray(T[] a)
方法,你需要传入一个与 element 类型相同的数组。此外你还要清楚,当传入数组大小大于或小于 element 个数时是如何处理的:
- 数组大小 > element 个数,数组超出部分设置为
null
; - 数组大小 < element 个数,该方法会自动创建一个新的、大小刚好合适的数组。
所以在使用该方法时,应该传入一个大小刚好合适的数组。
default <T> T[] toArray(IntFunction<T[]> generator)
方法是 Java 11 新添加的,它也能返回一个类型正确的数组,下面是一个官方示例:
String[] y = x.toArray(String[]::new);
注意,后两个方法被认为是“数组”与“Collection”间的桥梁(广义来说,第一个方法也算)。
使用 addAll() 前必须先进行 NPE 判断
阿里巴巴 Java 开发手册强制要求:在使用 Collection
接口任何实现类的 addAll()
方法前,都要对输入的集合参数进行 NPE 判断。以 ArrayList
为例,其 addAll()
方法的第一行代码就是 Object[] a = c.toArray()
,如果传入为 null
,则直接抛出异常。
你可以使用 Objects.nonNull(Object obj)
方法,也可以手动判断。
基于 equals() 定义的方法
Collection
接口中,有些方法是基于 equals()
方法定义的,它们大多需要进行“判等”操作,例如contains(Object o)
和 remove(Object o)
。
以 contains(Object o)
为例,该方法用于判断当前 collection 中是否存在一个与传入参数 o
相同的 element,如果存在则返回 true
,否则返回 false
,而这种相等性判断就是基于 equals()
方法定义的。
如果将上段话用代码描述,你可能会直观的认为是:如果当前 collection 中存在一个 e,满足 e.equals(o)
为 true
,则 contains(Object o)
返回为 true
,否则返回为 false
。
但你忽略了入参 o
为 null
的情况,这里我需要再次声明:collection 并未规定 element 不能为 null
。由于 equals()
方法需要满足对称性(如 a.equals(b)
与 b.equals(a)
的返回值应该相同),所以equals()
并不能处理 element 为 null
的情况。
因此更准确的描述是:如果当前 collection 中存在一个 e,满足 o==null ? e==null : o.equals(e)
的结果为 true
,则 contains(Object o)
返回为 true
,否则返回为 false
。
另外,虽然 containes()
方法是基于 equals()
方法定义的,但并不意味着调用 containrs()
方法就一定会执行其内部的 equals()
方法,因为一般会先比较 hashcode,如果 hashcode 都不相同,则没有必要再执行 equals()
方法。
hashCode() 和 equals() 方法
这个知识点算是老调重弹了,总结起来就是:
- 重写
equals()
,则必须重写hashCode()
; - set 存储的对象和 map 的 key 存储的对象,必须重写
hashCode()
和equals()
方法。
根据我的经验,你只需要记住:如果 equals()
返回 true
,则 hashCode()
则一定返回 true
,反之则不成立。