方法总览
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(); // ↑ IterableObject[] 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); // ↑ Objectint hashCode(); // ↑ Objectdefault Spliterator<E> spliterator() {...} // Iterabledefault 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 ,反之则不成立。
