zoukankan      html  css  js  c++  java
  • (二)一起学 Java Collections Framework 源码之 AbstractCollection

    .

    .

    .

    .

    .

    目录

    (一)一起学 Java Collections Framework 源码之 概述

    (二)一起学 Java Collections Framework 源码之 AbstractCollection

    java.util.AbstractCollection 类提供了 java.util.Collection 接口的骨干实现,也是 Java 集合框架(JCF, Java Collections Framework)中列表(List/Set)族相对较为顶层的实现类,这一点大家通过上一篇博文的 图1 就可以看出来了。此类是抽象类,因此仅仅实现了 Collection 接口中一些最基本的方法。由于此类没有实现 List 接口,所以它的子类未必都是有序的,因此它可以作为 List(有序)实现类和 Set(无序)实现类的共同祖先类。

    JCF 为容器的基本实现提供了多个 Abstract 类,因此掌握了这些类之后,我们自己基于这些 Abstract 类再实现新的容器时只要实现那些必要的方法即可。

    下面我们对 AbstractCollection 类中一些常见的方法实现逐个进行分析。

    1.iterator() 方法

    public abstract Iterator<E> iterator();

     此方法返回一个迭代器,该迭代器用于遍历这个列表中的每一个元素。由于 java.util.Iterator 也是一个接口,所以不同的数据结构实现(也就是不同的子类)可以根据自己的需要来定义不同的迭代方式,所以此方法被定义为一个抽象方法,没有对它进行实现。

    2.isEmpty() 方法

    1 public boolean isEmpty() {
    2     return size() == 0;
    3 }

    这个很好理解,用来判断当前集合是不是空的,不做过多的解释。

    3.contains(Object o) 方法

     1 public boolean contains(Object o) {
     2     Iterator<E> it = iterator();
     3     if (o==null) {
     4         while (it.hasNext())
     5             if (it.next()==null)
     6                 return true;
     7     } else {
     8         while (it.hasNext())
     9             if (o.equals(it.next()))
    10                 return true;
    11     }
    12     return false;
    13 }

    此方法用来判断当前集合中是否包含一个与传入的参数 o 相同的元素。

    想要匹配某个元素就必须要遍历集合了,于是先获得迭代器,然后根据参数 o 是否为 null 分别进行不同的处理。

    这里需要注意的是,在参数 o 不为 null 时采用的是 equals 方法来对比的。如果我们要使用特定的规则来判断这个对象是否存在于某个集合中,通过重写该对象的 equals() 方法即可实现。

    4.toArray() 方法

     1 public Object[] toArray() {
     2     // Estimate size of array; be prepared to see more or fewer elements
     3     Object[] r = new Object[size()];
     4     Iterator<E> it = iterator();
     5     for (int i = 0; i < r.length; i++) {
     6         if (! it.hasNext()) // fewer elements than expected
     7             return Arrays.copyOf(r, i);
     8         r[i] = it.next();
     9     }
    10     return it.hasNext() ? finishToArray(r, it) : r;
    11 }
    12 
    13 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    14 
    15 @SuppressWarnings("unchecked")
    16 private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
    17     int i = r.length;
    18     while (it.hasNext()) {
    19         int cap = r.length;
    20         if (i == cap) {
    21             int newCap = cap + (cap >> 1) + 1;
    22             // overflow-conscious code
    23             if (newCap - MAX_ARRAY_SIZE > 0)
    24                 newCap = hugeCapacity(cap + 1);
    25             r = Arrays.copyOf(r, newCap);
    26         }
    27         r[i++] = (T)it.next();
    28     }
    29     // trim if overallocated
    30     return (i == r.length) ? r : Arrays.copyOf(r, i);
    31 }
    32 
    33 private static int hugeCapacity(int minCapacity) {
    34     if (minCapacity < 0) // overflow
    35         throw new OutOfMemoryError
    36             ("Required array size too large");
    37     return (minCapacity > MAX_ARRAY_SIZE) ?
    38         Integer.MAX_VALUE :
    39         MAX_ARRAY_SIZE;
    40 }

    toArray() 方法调用了两个私有方法,虽然看起来比较长,但解读起来并不复杂。

    这个方法用于将集合中所有元素转换为数组。

    首先创建一个与集合长度相同的数组 r,并通过迭代器将集合中的每一个元素按照迭代的顺序依次保存到数组 r 中。

    但由于获取集合长度的方法 size() 是由子类来实现的,所以无法避免两种情况发生:

    1) size() 返回的长度比真正集合中元素的数量多;

    2) size() 返回的长度比真正集合中元素的数量少。

    数组 r 的长度又是固定的,怎么办?

    第一种情况,通过 java.util.Arrays.copyOf(Object[] original, int newLength) 方法根据真正的元素数量再创建一个新的数组,然后将 r 内现有的元素拷贝进去再返回即可。

    第二种情况稍微麻烦一点,因为此时并不知道集合中还有多少个元素没有被迭代出来,因此是不能直接确定新分配的数组的长度的。

    这时候两个私有函数就派上用场了:finishToArray(T[] r, Iterator<?> it) 方法会继续迭代这个集合,在遍历的过程中会通过 java.util.Arrays.copyOf(Object[] original, int newLength) 方法分配一个新的数组并赋给变量 r,长度是原先 r 数组的1.5倍左右(newCap = cap + (cap >> 1) + 1),然后继续将集合中后面迭代出来的元素按顺序保存到数组 r 中。直到 r 再次满了,那么再重复上面的步骤继续分配空间。

    最后一行 return (i == r.length) ? r : Arrays.copyOf(r, i); 的意思是,若按照前面每次分配 1.5 倍新空间的增长方式,若最终迭代完毕后整好填满了数组 r,那么直接返回 r 就可以了;若新分配的空间并没有完全被用完集合就遍历结束了,那么再重新分配一个与真实元素数量相同长度的数组来保存 r 的数据,并返回给调用者。关于这一点,大家看一下 java.util.Arrays.copyOf(Object[] original, int newLength) 的源码就明白了。

    私有函数 hugeCapacity(int minCapacity) 的作用是检查分配的元素数量有没有超过上限(MAX_ARRAY_SIZE)。

    5.toArray(T[] a) 方法

     1 @SuppressWarnings("unchecked")
     2 public <T> T[] toArray(T[] a) {
     3     // Estimate size of array; be prepared to see more or fewer elements
     4     int size = size();
     5     T[] r = a.length >= size ? a :
     6               (T[])java.lang.reflect.Array
     7               .newInstance(a.getClass().getComponentType(), size);
     8     Iterator<E> it = iterator();
     9 
    10     for (int i = 0; i < r.length; i++) {
    11         if (! it.hasNext()) { // fewer elements than expected
    12             if (a == r) {
    13                 r[i] = null; // null-terminate
    14             } else if (a.length < i) {
    15                 return Arrays.copyOf(r, i);
    16             } else {
    17                 System.arraycopy(r, 0, a, 0, i);
    18                 if (a.length > i) {
    19                     a[i] = null;
    20                 }
    21             }
    22             return a;
    23         }
    24         r[i] = (T)it.next();
    25     }
    26     // more elements than expected
    27     return it.hasNext() ? finishToArray(r, it) : r;
    28 }

    与 toArray() 方法相同,也是用于将集合转换为数组。

    这里需要注意的两点:

    1) 若参数 a 的长度小于集合的长度,此函数会在内部重新分配数组用于返回值,所以调用者的入参不会被改变。所以建议调用者使用其返回值而不是入参作为后续处理的数据源。

    2) 若参数 a 的长度大于集合的长度,则参数 a 比此集合多出来的部分会被赋为 null。

    大家不妨运行下面的代码,通过修改 len 来控制数组 ary0 的长度进行测试,观察运行结果。

     1 public static void main(String[] args) {
     2     AbstractCollection<String> c = new ArrayList<String>();
     3     c.add("a");
     4     c.add("b");
     5     c.add("c");
     6     c.add("d");
     7     int len = 5;
     8     String [] ary0 = new String [len];
     9     ary0[4] = "eee";
    10     String [] ary1 = c.toArray(ary0);
    11     print(ary0);
    12     System.out.println("ary1.length = " + ary1.length);
    13     print(ary1);
    14 }
    15 
    16 private static void print(String [] ary) {
    17     for (int i = 0; i < ary.length; i++) {
    18         System.out.println("ary[" + i + "]" + ary[i]);
    19     }
    20 }

    6.remove(Object o) 方法

     1 public boolean remove(Object o) {
     2     Iterator<E> it = iterator();
     3     if (o==null) {
     4         while (it.hasNext()) {
     5             if (it.next()==null) {
     6                 it.remove();
     7                 return true;
     8             }
     9         }
    10     } else {
    11         while (it.hasNext()) {
    12             if (o.equals(it.next())) {
    13                 it.remove();
    14                 return true;
    15             }
    16         }
    17     }
    18     return false;
    19 }

    此方法用于从集合中删除一个与参数 o 相同的元素。

    这个方法的实现与 contains(Object o) 方法十分相似,只不过是多调用了一下迭代器中的 remove() 方法而已。

    同样需要注意这里比较对象使用的是 equals() 方法,因此可以通过重写对象 equals() 方法的方式来自定义删除规则。

    7.containsAll(Collection<?> c) 方法

    1 public boolean containsAll(Collection<?> c) {
    2     for (Object e : c)
    3         if (!contains(e))
    4             return false;
    5     return true;
    6 }

    此方法用来测试当前集合是否包含集合 c 中的每一个元素。

    实现方式很简单,迭代集合 c,测试其每一个元素是否被包含在当前的集合中,若有任何一个元素不包含在当前集合中,则返回 false。

    8.addAll(Collection<? extends E> c) 方法

    1 public boolean addAll(Collection<? extends E> c) {
    2     boolean modified = false;
    3     for (E e : c)
    4         if (add(e))
    5             modified = true;
    6     return modified;
    7 }

    此方法将集合 c 中的每一个元素依次添加到本集合中。

    但请注意,只要集合 c 中的任何一个元素成功添加到了当前集合中,即使其它元素全部添加失败了,此方法也会返回 true。所以官方 API 文档对于返回值给出的解释是:如果此 collection 由于调用而发生更改,则返回 true。

    9.removeAll(Collection<?> c) 方法

     1 public boolean removeAll(Collection<?> c) {
     2     Objects.requireNonNull(c);
     3     boolean modified = false;
     4     Iterator<?> it = iterator();
     5     while (it.hasNext()) {
     6         if (c.contains(it.next())) {
     7             it.remove();
     8             modified = true;
     9         }
    10     }
    11     return modified;
    12 }

    此方法用于从当前的集合中移除所有与 c 集合相同成员的成员。此方法的作用并不是无条件清空本集合,若需如此做,要调用 clear() 方法。

    实现非常简单,迭代遍历,只要包含相同的成员就 remove() 掉。

    这里的 Objects.requireNonNull(c); 用于判断入参 c 是否为 null,若为 null 就跑出空指针异常。java.util.Objects 类是从 JDK 1.7 版本开始提供的。

    10.retainAll(Collection<?> c) 方法

     1 public boolean retainAll(Collection<?> c) {
     2     Objects.requireNonNull(c);
     3     boolean modified = false;
     4     Iterator<E> it = iterator();
     5     while (it.hasNext()) {
     6         if (!c.contains(it.next())) {
     7             it.remove();
     8             modified = true;
     9         }
    10     }
    11     return modified;
    12 }

    此方法与 removeAll(Collection<?> c) 方法恰恰相反,仅保留当前集合中与集合 c 中相同的元素,也就是说将本集合中与集合 c 中的不同的元素都删掉。

    实现方式与 removeAll(Collection<?> c) 方法完全相同,只有测试包含的条件是相反的。

    11.clear() 方法

    1 public void clear() {
    2     Iterator<E> it = iterator();
    3     while (it.hasNext()) {
    4         it.next();
    5         it.remove();
    6     }
    7 }

    无条件清空当前集合。迭代删除每一个元素即可。

  • 相关阅读:
    Windows_10 系统封装
    leetcode-75 颜色分类
    leetcode-922 按奇偶排序数组 II
    leetcode-905 按奇偶数排序
    UVA-10827 环面上的最大子矩阵和
    leetcode918 环形最大子数组
    leetcode-85 最大矩形
    leetcode-84 柱状图中的最大矩形
    leetcode-221 最大正方形
    leetcode-713 乘积小于k的数组
  • 原文地址:https://www.cnblogs.com/0xcafebabe/p/6659515.html
Copyright © 2011-2022 走看看