• 容器--Collection和AbstractCollection


    一、前言

         容器是JAVA中比较重要的一块,整个体系设计得非常好,同时对于代码学习来说也是比较好的范例。同时很多面试官也比较喜欢用容器来考察面试者的基础知识,所以掌握好容器还是比较重要的。本文主要总结一下所有容器的公共接口之一Collection以其抽象实现AbstractCollection.

    二、Collection介绍

         JDK的官方文档对Collection的定义是这样的:The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered and others unordered. 

      通过定义我们知道Collection表示一组对象,根据集合类型的不同,有的允许重复元素,有的是有序的,这个要看具体的子接口的实现情况。Collection接口中定义一些通用的方法。这些方法都比较基本而且使用都比较频繁,所以我们需要对每一个方法都记录,按方法的作用我们可以分为以下几类:

          1. 添加

          共两个方法,分别是add和addAll, 分别是接收一个对象和一个Collection对象。

          2. 删除

          共四个方法,remove, removeAll, retainAll,clear, 其中需要说的是retainAll,这个操作接受一个Collection作为参数,取两个集合的交集。

          3. 查找

          共两个方法, contains, containsAll, 判断集合中是否有某个或某些元素

          4. 转换

          共三个方法, toArray(), toArray(T t[])和iterator, 前两个是把集合转化为数组,另外一个是转化为一个Iterator对象,可用于遍历,这个方法其实在其父接口Iterable中也有定义。

          5. 求大小

          共两个方法, size()和isEmpty(), 分别是求长度和判断集合是否为空。

          6. 比较

          共两个方法,equals和hashCode,这两个方法是从object中继承过来的。

          以上共15个方法,大多数还是很好理解。需要重点关注的是转换类的两个方法,由于Collection继承了Iterable,所以所有的collection都可以通过foreach的方式来调用,这是JDK1.5之后的一种语法糖。

          关于Collection的介绍就到这里,下面接着看一下其直接骨架类AbstractCollection.

    三、AbstractCollection介绍

          虽然Collection中的方法很多,其不同子类型的表现也不一样,但事实上这15个方法中有很多都是跟具体的子类没有关系的,为了简化具体Collection类的设计, JDK提供了一个抽象类AbstractCollection,对其中的大多数方法进行了实现。

          方法的实现没有必要依次去介绍,这里主要介绍这个子类的一些特点及几个重要方法的实现。

          1. 本类默认是不是可修改的,即不支持add,由于addAll依赖于add,所以addAll也是不支持的,要支持添加功能,就需要重写这个add方法

          2. size,iterator这两个方法没有实现,所以要编写自己的collection,需要实现这两个方法即可。

          3. 其它所有方法都有实现,不过涉及到遍历的都依赖于iterator()返回的迭代器,删除也依赖于迭代器提供的删除方法。

          4. 本类没有对equals和hashCode进行重写,但是对toString进行了重写。

          大部分方法的实现很容易理解,下面重点介绍一下toArray。

    四、重点方法分析

         Collection可以直接转化为数组,本接口中有两个方法,Object[] toArray()和<T> T[] toArray(T[] a),相信有些人和我一样,在使用时会感觉到困惑,一是不知道使用哪个方法,二是不知道第二个方法的参数和返回值之间有什么关系,下面我们就来认真分析一下。

         先看一下JDK对于这个方法的定义描述:The returned array will be "safe" in that no references to it are maintained by this collection. (In other words, this method must allocate a new array even if this collection is backed by an array). The caller is thus free to modify the returned array.

          从这个描述我们可以知道toArray得到的数组跟原collection没有任何关系,我们可以对数组的每个引用值做修改,而不会影响到原collection.这个看起来好像是多余说明的,但是考虑到ArrayList其实就是基于数组实现的,那这个限制保证了即使是将ArrayList转化为数组,那也应该是分配一个新数组,而不是返回原来的数组。

          好了,我们再看一下具体的代码。

         

     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 <T> T[] finishToArray(T[] r, Iterator<?> it) {
    14         int i = r.length;
    15         while (it.hasNext()) {
    16             int cap = r.length;
    17             if (i == cap) {
    18                 int newCap = cap + (cap >> 1) + 1;
    19                 // overflow-conscious code
    20                 if (newCap - MAX_ARRAY_SIZE > 0)
    21                     newCap = hugeCapacity(cap + 1);
    22                 r = Arrays.copyOf(r, newCap);
    23             }
    24             r[i++] = (T)it.next();
    25         }
    26         // trim if overallocated
    27         return (i == r.length) ? r : Arrays.copyOf(r, i);
    28     }
    29 
    30 
    31     private static int hugeCapacity(int minCapacity) {
    32         if (minCapacity < 0) // overflow
    33             throw new OutOfMemoryError
    34                 ("Required array size too large");
    35         return (minCapacity > MAX_ARRAY_SIZE) ?
    36             Integer.MAX_VALUE :
    37             MAX_ARRAY_SIZE;
    38     }

    上面是AbstractCollection的实现,可以看到对于toArray()来说,就是分配了一个等大空间的数组,然后依次对数组元素进行赋值。

    如果我们在单线程操作的情况下,collection集合大小不变,正常应该是执行到 return it.hasNext() ? finishToArray(r, it) : r; 这条语句结束,但考虑到在复制的过程中,collection的集合可能会有变化,可能是变大也可能是变小,所以方法增加了对这种情况的处理,这就是为什么每次循环都要判断是collection是否遍历完,以及最后再判断collection是否变得更长,如果是的话,还需要重新再为array分配空间。

    通常情况下,我们不会执行到hugeCapacity,但作为一个框架来说,这体现了设计时的严谨。

    可以看到,toArray返回的是一个Object数组,不能很好的体现collection中的元素类型,这样collection的泛型就无法体现出优势。所以,我们又有了第二个方法。个人当时在使用这个方法是,最大的疑惑就在于不知道这个参数应该怎么传,下面我们来看下具体的实现。

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

    我们可以看到,方法在处理里,会先判断参数数组的大小,如果空间足够就使用参数作为元素存储,如果不够则新分配一个。在循环中的判断也是一样,如果参数a能够存储则返回a,如果不能再新分配。在看了这个之后,对于这新代码strList.toArray(new String[0])相信就很容易理解了。

    在看了这两个方法后,我相信对于toArray的区别和使用就比较容易掌握了,个人建议还是使用第二种比较好一些,在参数的选择上,要么传递一个0长度的数组,要么就传递一个与集合等长的数组,但考虑到集合的可变性,我们应该使用这个方法的返回值,而不是直接使用参数数组。

    五、总结

    总的来说,Collection和AbstractCollection还是比较简单,但只有掌握了这两个简单的类,在学习后续的各种list和set的时候,我们才能更好的理解。

          

  • 相关阅读:
    牛客练习赛51 D题
    Educational Codeforces Round 72 (Rated for Div. 2) C题
    Codeforces Round #583 (Div. 1 + Div. 2, based on Olympiad of Metropolises) C题
    Codeforces Round #583 (Div. 1 + Div. 2, based on Olympiad of Metropolises) A题
    Codeforces Round #583 (Div. 1 + Div. 2, based on Olympiad of Metropolises) A题
    Educational Codeforces Round 72 (Rated for Div. 2) B题
    Educational Codeforces Round 72 (Rated for Div. 2) A题
    《DSP using MATLAB》Problem 7.2
    《DSP using MATLAB》Problem 7.1
    《DSP using MATLAB》Problem 6.24
  • 原文地址:https://www.cnblogs.com/macs524/p/5721036.html
Copyright © 2020-2023  润新知