前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java基础系列(四十):集合之AbstractCollection

Java基础系列(四十):集合之AbstractCollection

作者头像
山禾说
发布2019-01-21 10:11:50
3900
发布2019-01-21 10:11:50
举报
文章被收录于专栏:Vi的技术博客Vi的技术博客

前言

(PS:上周由于准备自考,更新延误了一些,见谅!) AbstractCollection类是Collection接口的一个实现类,它只是为了给我们提供了一些最基础的接口的实现,在实际的应用中,我们并不会去使用这个类来封装一些信息,接下来我们首先去看一下这个类的继承关系图。

结构图

由此可以看出AbStractCollection只是实现了Collection接口,并没有实现或者继承其他的类或者接口。接下来,我们来看一下这个类的源码,看看会有什么收获。

源码

package java.util;

//TAG 1
public abstract class AbstractCollection<E> implements Collection<E> {


    protected AbstractCollection() {
    }


    public abstract Iterator<E> iterator();

    public abstract int size();

    //TAG 2
    public boolean isEmpty() {
        return size() == 0;
    }

    //TAG 3
    public boolean contains(Object o) {
        Iterator<E> it = iterator();
        if (o==null) {
            while (it.hasNext())
                if (it.next()==null)
                    return true;
        } else {
            while (it.hasNext())
                if (o.equals(it.next()))
                    return true;
        }
        return false;
    }

    //TAG 3
    public Object[] toArray() {
        Object[] r = new Object[size()];
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) 
                return Arrays.copyOf(r, i);
            r[i] = it.next();
        }
        return it.hasNext() ? finishToArray(r, it) : r;
    }

    //TAG 3
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        int size = size();
        T[] r = a.length >= size ? a :
                  (T[])java.lang.reflect.Array
                .newInstance(a.getClass().getComponentType(), size);
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) { 
                if (a == r) {
                    r[i] = null;
                } else if (a.length < i) {
                    return Arrays.copyOf(r, i);
                } else {
                    System.arraycopy(r, 0, a, 0, i);
                    if (a.length > i) {
                        a[i] = null;
                    }
                }
                return a;
            }
            r[i] = (T)it.next();
        }
        return it.hasNext() ? finishToArray(r, it) : r;
    }

    //TAG 4
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    //TAG 5
    @SuppressWarnings("unchecked")
    private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
        int i = r.length;
        while (it.hasNext()) {
            int cap = r.length;
            if (i == cap) {
                int newCap = cap + (cap >> 1) + 1;

                if (newCap - MAX_ARRAY_SIZE > 0)
                    newCap = hugeCapacity(cap + 1);
                r = Arrays.copyOf(r, newCap);
            }
            r[i++] = (T)it.next();
        }

        return (i == r.length) ? r : Arrays.copyOf(r, i);
    }

    //TAG 6
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError
                ("Required array size too large");
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

    //TAG 7
    public boolean add(E e) {
        throw new UnsupportedOperationException();
    }


   //TAG 3
    public boolean remove(Object o) {
        Iterator<E> it = iterator();
        if (o==null) {
            while (it.hasNext()) {
                if (it.next()==null) {
                    it.remove();
                    return true;
                }
            }
        } else {
            while (it.hasNext()) {
                if (o.equals(it.next())) {
                    it.remove();
                    return true;
                }
            }
        }
        return false;
    }

    //TAG 3
    public boolean containsAll(Collection<?> c) {
        for (Object e : c)
            if (!contains(e))
                return false;
        return true;
    }

    //TAG 3
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }

    //TAG 3
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<?> it = iterator();
        while (it.hasNext()) {
            if (c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }


    //TAG 3
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }

    //TAG 3
    public void clear() {
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            it.next();
            it.remove();
        }
    }

    //TAG 3
    public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }
}

TAG 1 : 从关键字abstract可以看出这个类是一个抽象类,这个类只是实现了一部分的接口,还遗留了一部分接口需要我们在子类中去实现。

TAG 2

isEmpty()方法通过size()接口是否等于0来判断,而size()接口的实现交给了更为具体的集合类去实现。

TAG 3contains()remove()containsAll()addAll()removeAll()retainAll()clear()toString()这些方法我们通过阅读源码可以知道,都是通过迭代器来实现的,其中有一些方法使用了增强for循环,但其实编译器会将增强for循环编译为使用迭代器的遍历操作。

TAG 4 : 数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8 byte。

TAG 5 : finishToArray(T[] r, Iterator<?> it)方法用于数组扩容,当数组索引指向最后一个元素+1时,对数组进行扩容:即创建一个大小为(cap + cap/2 +1)的数组,然后将原数组的内容复制到新数组中。扩容前需要先判断是否数组长度是否溢出。这里的迭代器是从上层的方法(toArray(T[] t))传过来的,并且这个迭代器已执行了一部分,而不是从头开始迭代的

TAG 6hugeCapacity(int minCapacity)方法用来判断该容器是否已经超过了该集合类默认的最大值即(Integer.MAX_VALUE -8),一般我们用到这个方法的时候比较少,后面我们会在ArrayList类的学习中,看到ArrayList动态扩容用到了这个方法。

TAG 7 : 这里的add(E)方法默认抛出了一个异常,为什么这样去定义呢?而不是直接定义为抽象方法? 如果我们想修改一个不可变的集合时,抛出 UnsupportedOperationException 是正常的行为,比如当你用 Collections.unmodifiableXXX() 方法对某个集合进行处理后,再调用这个集合的修改方法(add,remove,set…),都会报这个错。因此 AbstractCollection.add(E) 抛出这个错误是准从标准。 而之所以没有定义为抽象方法,是因为可能有很多地方用不到这个方法,用不到还必须实现,这岂不是让人很困惑么。(PS:参考自拭心)

toArray详解

在这个类中,我们需要详细了解的方法是toArray,正如其名,它可以把一个集合转换为数组,可以看到toArray方法分为了两种参数的方法:

    /**
    * 分配了一个等大空间的数组,然后依次对数组元素进行赋值
    */
    public Object[] toArray() {
        //新建等大的数组
        Object[] r = new Object[size()];
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            //判断是否遍历结束,以防多线程操作的时候集合变得更小
            if (! it.hasNext()) 
                return Arrays.copyOf(r, i);
            r[i] = it.next();
        }
         //判断是否遍历未结束,以防多线程操作的时候集合变得更大,进行扩容
        return it.hasNext() ? finishToArray(r, it) : r;
    }


    /**
    * 泛型方法的`toArray(T[] a)`方法在处理里,会先判断参数数组的大小,
    * 如果空间足够就使用参数作为元素存储,如果不够则新分配一个。
    * 在循环中的判断也是一样,如果参数a能够存储则返回a,如果不能再新分配。
    */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        int size = size();
        //当数组a的长度大于等于a,直接将a赋予给r,否则使用反射API获取一个长度为size的数组
        T[] r = a.length >= size ? a :
                  (T[])java.lang.reflect.Array
                .newInstance(a.getClass().getComponentType(), size);
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            //判断是否遍历结束
            if (! it.hasNext()) { 
                //如果 a == r,将r的每项值赋空,并将a返回
                if (a == r) {
                    r[i] = null;
                } else if (a.length < i) {
                    //如果a的长度小于r,直接调用Arrays.copyOf进行复制获取一个新的数组
                    return Arrays.copyOf(r, i);
                } else {
                    System.arraycopy(r, 0, a, 0, i);
                    if (a.length > i) {
                        a[i] = null;
                    }
                }
                return a;
            }
            //如果遍历结束,将迭代器获取的值赋给r
            r[i] = (T)it.next();
        }
        //判断是否遍历未结束,以防多线程操作的时候集合变得更大,进行扩容
        return it.hasNext() ? finishToArray(r, it) : r;
    }

    /**
    * 设定该容器的最大值
    */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
    *   用于动态扩容
    */
    @SuppressWarnings("unchecked")
    private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
        int i = r.length;
        while (it.hasNext()) {
            int cap = r.length;
            if (i == cap) {
                int newCap = cap + (cap >> 1) + 1;

                if (newCap - MAX_ARRAY_SIZE > 0)
                    newCap = hugeCapacity(cap + 1);
                r = Arrays.copyOf(r, newCap);
            }
            r[i++] = (T)it.next();
        }

        return (i == r.length) ? r : Arrays.copyOf(r, i);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError
                ("Required array size too large");
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

为了帮助了解,我把Arrays.copyOf(r.i)的源码也贴出来:

//参数original代表你传入的需要复制的泛型数组,newLength复制得到数组的大小
public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

我们可以观察到其中调用了System.arraycopy方法,为了保持刨根问底的态度,我们又去翻看了这个方法的源码:

 //src数组里从索引为srcPos的元素开始, 复制到数组dest里的索引为destPos的位置, 复制的元素个数为length个. 
 public static native void arraycopy(Object src, int srcPos, Object dest, int destPos,int length);

可以看到这个方式是由关键字native修饰的方法,那么native修饰的方法有什么含义呢? native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

JNI是Java本机接口(Java Native Interface),是一个本机编程接口,它是Java软件开发工具箱(java Software Development Kit,SDK)的一部分。JNI允许Java代码使用以其他语言编写的代码和代码库。Invocation API(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用Java代码。

然后我们来分析toArray()中需要注意的点,通过原源码中的英文注解,toArray得到的数组跟原collection没有任何关系,我们可以对数组的每个引用值做修改,而不会影响到原collection.这个看起来好像是多余说明的,但是考虑到ArrayList其实就是基于数组实现的,那这个限制保证了即使是将ArrayList转化为数组,那也应该是分配一个新数组,而不是返回原来的数组。

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

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

下节预告

下节课,我们会刨根问底的去了解List的使用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-10-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Vi的技术博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 结构图
  • 源码
  • toArray详解
  • 下节预告
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档