方法总览

Collection 接口是 Java 集合框架 Collection 部分的根接口,在官方注释中,将 Collection 定义为 a group of objects,并将其中包含的对象称为 element。该接口声明的方法如下所示:

  1. public interface Collection<E> extends Iterable<E> {
  2. /* Query Operations 查询操作 */
  3. int size();
  4. boolean isEmpty();
  5. boolean contains(Object o);
  6. Iterator<E> iterator(); // ↑ Iterable
  7. Object[] toArray();
  8. <T> T[] toArray(T[] a);
  9. default <T> T[] toArray(IntFunction<T[]> generator) {...} // since 11
  10. /* Modification Operations 改动操作 */
  11. boolean add(E e);
  12. boolean remove(Object o);
  13. /* Bulk Operations 批量操作 */
  14. boolean containsAll(Collection<?> c);
  15. boolean addAll(Collection<? extends E> c);
  16. boolean removeAll(Collection<?> c);
  17. default boolean removeIf(Predicate<? super E> filter) {...}
  18. boolean retainAll(Collection<?> c);
  19. void clear();
  20. /* Comparison and hashing 比较和哈希 */
  21. boolean equals(Object o); // ↑ Object
  22. int hashCode(); // ↑ Object
  23. default Spliterator<E> spliterator() {...} // Iterable
  24. default Stream<E> stream() {...}
  25. default Stream<E> parallelStream() {...}
  26. }

这里列出方法仅是为了让你有一个直观的印象,需要掌握的知识点已经在下面罗列好了。

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

但你忽略了入参 onull 的情况,这里我需要再次声明: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 ,反之则不成立。