【Java源码计划】Collection|【Java源码计划】Collection

Collection是集合的根接口,继承自Iterable接口,用于表示一组元素数据,有的集合允许重复元素,有的不允许,有的有序有的无序。JDK中没有这个接口的直接实现,有更加具体的接口比如set和list,这个接口通常用于传递集合,能够提供最高级别的抽象。
背包或者多重集合应该直接实现这个接口,背包是一种只放不能做删除的数据结构,多重集是指没有排序并且允许重复的集合。
所有通用的Collection实现类,一般来说是通过其子接口间接实现,一般应该提供两个标准的构造函数,第一个是没有参数的构造函数,用于创建一个空的集合,另一个是有一个参数的构造函数,接收一个collection类型的参数,用于使用现有的元素创建集合。通过这个构造函数,可以复制任何集合。这个规定没有强制性,因为接口不能写构造函数,但是所有Java平台的集合实现都遵守这个要求。
对于接口中包含的具有“破坏性”的方法,也就是操作后会对数据有所修改的方法,当集合不支持这种操作的时候可以通过抛出UnsupportedOperationException来表示。在这个操作没有修改数据的情况下,也可以抛出UnsupportedOperationException来表示,但是这不是一个强制的要求。比如addAll方法在一个集合不可修改的实现类中,集合中没有元素,这时候可能会抛出一个异常,但是这个仍然是不做强制的。
有的集合实现类对于放入其中的元素有约束,比如有的不允许null存入,还有的可能会对类型做校验。尝试向集合中添加非法的元素时会抛出未检查异常,典型的比如 空指针异常NullPointerException和类转换错误异常ClassCastException。尝试去查询一个非法元素,可能会抛出一个异常或者只是简单的返回false,不同实现类的表现可能不一样,有的可能符合前者,有的可能符合后者。更一般来说,尝试对不符合条件的元素进行操作,可能不会将不合格元素插入到集合中,而是抛出异常,也可能会成功执行。 这些异常在此接口的规范中被标记为“可选”,也就是说实现类可以选择执行或者不执行。
每个集合决定自己的同步策略。 在没有实现类提供的更强保证的情况下,产生不可预测的接口行为可能是由于另一个线程访问集合方法导致的; 这包括直接调用,将集合传递给可能执行调用的方法,并使用现有的迭代器来检查集合。
整个集合框架中的很多方法的实现都依赖于Object.equals方法,例如contains方法,但是我们在理解的时候不应该认为,非空参数一定会调用equals方法去比较相等,这得实现可以是自由的,比如可以首先比较哈希码,更一般地说,集合框架的接口实现的时候可以随意使用底层Object的方法,只要实现者认为合适即可。
由于自包含,无论是间接地还是直接的集合包含自己,集合在进行递归遍历的时候可能会失败并抛出异常这里面包括了clone,equals,hashcode和toString方法,实现类可以选择解决自引用问题,虽然现在多数集合并没有这么做。
注意,集合接口中的默认实现里没有任何处理同步的能力,如果需要处理同步的情况必须覆盖重写这些方法。
接口中主要包括几类接口,
第一类——查询操作

  1. size(); 用于返回集合中的元素数量,返回一个整形,但是如果集合中元素数量超过了最大的整数(Integer.MAX_VALUE)只返回最大整数(Integer.MAX_VALUE)
  2. isEmpty() 用于判断集合是否为空,返回boolean类型,true标示集合不含有任何元素
  3. contains(Object o); 当集合中存在最少一个传入的元素时返回true,包括当o为null时,存在null元素,或者o不为null时,o.equals(e)两种情况,也就是说contains方法判断元素是否相同时,null是一种,除此以外全部依赖于equals方法。
  4. iterator(); 返回一个迭代器的集合,这个方法用于迭代遍历,但是注意,在这个接口中方法不为元素顺序提供担保,但是不排除有的实现类可以提供担保。
  5. toArray(); 这个方法返回包含所有元素的数组,但是注意,如果这个实现类对元素顺序在迭代器中提供过担保,那么此处返回的元素也必须按照相同的顺序。另外返回的数组必须是安全的,也就是说不能有任何引用的,换句话说这个方法必须为生成的数组重新申请空间。这个方法是数组和集合之间的桥梁。
  6. toArray(T[] a); 也是返回一个包含所有元素的数组,不同之处在于,返回时的类型与给定的类型相同,如果给的数组的大小足以存储元素,那就用原来的存储否则会用运行时的类型重新申请一个数组,大小是当前集合的size。如果集合中元素放入后还有空余,比如原有数组中元素比现在集合多,那么超过的部分会立即被设置为null。当调用者确定自己的集合中不包含任何为null的元素时,这个对于确定集合长度很有用。与上一个方法一样,如果类的实现提供了任何对于顺序的担保,那么此时也应该按照相同的顺序输出,这个方法也是一个连接数组和集合的桥梁。并且,这个方法支持更精确的类型控制,在某种情况下,可以用于节省分配的成本。
    假如x是一个已知的集合,其中只包含字符串,下面的代码可以用于备份集合中的元素到一个新的数组中(注:下面这种形式和方法5是等价的)
String[] y = x.toArray(new String[0]);

第二类——修改操作
  1. add(E e); 添加方法,对于传入的类型,在collection接口层面的要求是可以校验类型与collection匹配,也可以不校验,这个作为一个可选的实现。如果集合有所修改,那么返回true。注意,返回false时,可能是集合类不允许重复,并且其中已经有了对应的元素。支持这个操作的集合类可能会对元素做出限制,比如,有的集合可能会拒绝存储null元素,还有的可能加强对存入元素的类型的约束。同时collection接口要求各个集合必须在文档中清楚地阐述这些约束。同时这个接口做出了规定,除非是包含这个元素,其他的拒绝添加元素进入集合必须抛出异常而不能返回false。这个机制完成了了调用正常返回后,集合中一定包含这个元素的保证。抛出的异常包括
    UnsupportedOperationException 集合不支持添加
    ClassCastException 类型转换错误,类型不对
    NullPointerException 添加的元素是null,并且集合不允许添加null。
    IllegalArgumentException 如果元素有什么属性是被拒绝加入到集合中的
    IllegalStateException 如果此时不能添加这个元素,受到插入限制
  2. remove(Object o); 移除操作,这个方法会移除第一个匹配的元素,所谓匹配包括o如果为null,匹配null,不为null则调用equals方法判断。只有移除成功才会返回true,换句话说只有当包含这个元素的时候才会返回true;
第三类——批量操作
  1. containsAll(Collection c); 当集合中含有传入集合中的所有元素的时候返回true。
  2. addAll(Collection c); 添加传入的集合中所有元素到现有的集合中。如果在处理过程中,集合被修改了,那么所产生的结果是不确定的。也就是说,如果添加自己,并且不为空那么这种行为是未定义的。
  3. removeAll(Collection c); 移除所有在两个集合中都存在的元素,当这个方法执行后,当前集合不含有任何与传入集合相同的元素。
  4. default boolean removeIf(Predicate filter); 这个方法在接口中有方法体,注意这是JDK1.8中加入的。注意区分以前可能有人跟你说访问限制的那个所谓的访问关键字允许接口中实现默认的方法。另外多接口实现的时候如果有签名相同的default方法,会出现编译错误。方法用于移除满足给出的filter条件的元素。默认实现,也就是这个collection中的这个方法实现中会遍历所有元素,然后移除符合条件的,但是当移除第一个元素时,如果迭代器不允许移除,那么会抛出异常。
  5. retainAll(Collection c); 方法只保存集合中那些出现在传入集合中的元素,换句话说,方法会移除所有不在给定集合中的元素。
  6. clear(); 移除当前集合中的所有元素,方法执行后集合为空的集合。
第四类——比较和哈希操作
  1. equals(Object o); 将给定的Object与集合进行比较,来判断是否相等。
  2. hashCode(); 哈希code方法。
  3. default Spliterator spliterator(); 也是一个default方法,从JDK1.8开始加入,同时1.8引入了了一个Spliterator,用于并行遍历元素而设计的一个迭代器。这里是为了用于获取spliterato的方法,具体有关spliterator的内容放到专门对应的类解析中讲,在这里先理解为一种牛掰的iterator就好。在这里有个东西叫做特征值,需要特殊讲一我们也放到Spliterator中说。此处只要知道Collocation接口中要求,除了Spliterator报告的特征值是SIZED,并且集合中不含有元素以外,必须记录特征值。如果想返回一个更高效的spliterator的话,子类应该重写这个默认方法。为了满足Stream()和parallelStream这类方法的懒加载行为,Spliterator需要具备IMMUTABLE或者CONCURRENT特征值,或者可以延迟绑定。如果这些都没能实现,那么重写的类需要描述清楚绑定的策略,或者接口,并重写Stream()和parallelStream方法来创建流。比如如下的写法:
Stream s = StreamSupport.stream(() -> spliterator(), spliteratorCharacteristics)

这些要求确保由stream()和parallelStream()方法生成的流能能够在启动时反映到集合中原始的元素上。
  1. default Stream stream()返回集合中元素按序排列的流,如果上一个方法无法返回具有IMMUTABLE或者CONCURRENT特征值的spliterator,或者能够延迟绑定的spliterator,那么这个方法就需要被重写
  2. default Stream parallelStream() 返回一个可能得并行流,其他要求同上。
【【Java源码计划】Collection|【Java源码计划】Collection】这个接口就说完了,总的来说相比于JDK1.7的比较容易理解,由于JDK1.8的新特性,所以理解起来需要先对1.8有所理解。没关系,我们暂时挂起,回头碰到了的时候再说。

    推荐阅读