资讯专栏INFORMATION COLUMN

ArrayList的克隆与toArray

codeKK / 2478人阅读

摘要:概述列表是一款即实用又常用的数据结构,用来存储线性结构的数据。在中对的支持主要有两种,也是最常用的两种。本文主要分析的源码。的底层主要是基于链表来实现的。但是返回的却没有这样的等同关系。那么其方法返回的只是一个类型的数组,而不是类型。

概述

列表(list)是一款即实用又常用的数据结构,用来存储线性结构的数据。在JDK中对List的支持主要有两种,也是最常用的两种。一种是ArrayList,一种是LinkedList

而且这两种list的区别也经常出现在节操公司的面试题中。节操高一点可能还会问某种list的具体实现,下面说说这两种List的区别。本文主要分析ArrayList的源码。

ArrayList

ArrayList的底层主要是基于数组来实现的。
众所周知,数组是有get√到RandomAccess技能的,也就是说,array[index]这个操作的时间复杂度为O(1)。
但是,在数组上进行增删操作,就需要比较费力了,往数组中插入一项,需要将其后面的所有项都向后移动一个单元,删除数组的一项,则需要把后面的所有项都向前移动一位。
最好的情况下,你要插入或者删除的那一项刚好位于数组的末端,那么就无需再移动任何数据。
所以,如果增删操作比较多,则不应该选用基于数组实现的ArrayList。

LinkedList

LinkedList的底层主要是基于链表来实现的。
链表的优势就是增删操作不需要像ArrayList一样拷贝数组(移动意味着拷贝),只需要更改前后节点的引用。菊葛丽子,节点A和节点B中间需要插入一个节点C。一开始,A的后继是B,B的前驱是A(不明白前驱后继这两个术语请查找数据结构方面的书),要插入C时,只需要做以下几个操作:

将A的后继改为C

将C的前驱改为A

将C的后继改为B

将B的前驱改为C。

删除操作同理可推。
但是,LinkedList要进行随机查找可就麻烦了,必须得先从链表的一端(从头部或者尾部)开始找,我要查找第10个,它必须先找到第1个,然后第2个,... ,第9个,然后才是第十个。因此,LinkedList适合增删操作比较多的场景。
可以看看JDK源码中LinkedList的访问函数:

    Node node(int index) {
        // assert isElementIndex(index);
        if (index < (size >> 1)) {
            Node x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
深拷贝与浅拷贝

翻开ArrayList的源码,可以看到其中有一个clone()方法,用于克隆当前ArrayList实例。
其方法实现如下:

    /**
     * Returns a shallow copy of this ArrayList instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this ArrayList instance
     */
    public Object clone() {
        try {
            @SuppressWarnings("unchecked")
                ArrayList v = (ArrayList) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn"t happen, since we are Cloneable
            throw new InternalError();
        }
    }

可以看到,先调用了super.clone()方法复制了一个全新的对象,并把它赋值给v。由于java.lang.Object.clone()只是一种浅复制,所以,v的elementData引用的还是当前ArrayList的elementData的引用,这就意味着,在v上做操作,会影响原来的ArrayList的值。这也是为什么需要再跟上这么一个语句的原因v.elementData = Arrays.copyOf(elementData, size);。这句话的作用是对原来ArrayList的elementData进行一个数组拷贝,然后赋值给v的elementData,这样,v的elementData就是原先ArrayList的elementData的一个副本,不再是内存中同一块数组。在v上的操作也不会影响到原先的ArrayList。这才是ArrayList的clone()方法真正想做的事情。

toArray的坑

在ArrayList的构造函数中,可以看到有这样一个构造函数:

    public ArrayList(Collection c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

这个构造函数用另外一个集合中的元素来构造ArrayList。
但是,注意到其中一句注释
c.toArray might (incorrectly) not return Object[] (see 6260652)
说c的toArray()方法有可能不返回类型为Object[]的数组。并且,在jdk的bug列表中编号为6260652的bug可以找到解释:

A DESCRIPTION OF THE PROBLEM :
The Collection documentation claims thatcollection.toArray()is "identical in function" tocollection.toArray(new Object[0]);
However, the implementation of Arrays.asList does not follow this: If created with an array of a subtype (e.g. String[]), its toArray() will return an array of the same type (because it use clone()) instead of an Object[].
If one later tries to store non-Strings (or whatever) in that array, an ArrayStoreException is thrown.
Either the Arrays.asList() implementation (which may return an array of component type not Object) or the Collection toArray documentation (which does not allow argumentless toArray() to return arrays of subtypes of Object) is wrong.

在Java中,数组是不具备有兼容性的,举个例子:

    /**
     *
     *
     * @author Bean
     * @date 2015年7月15日 下午5:14:35
     * @version 1.0
     *
     */
    public class Box {
    
        public static void main(String[] args) {
            Object[] obj1 = new Object[1];
            obj1[0] = new Student();
            obj1[0] = new Person();
            
            Object[] obj2 = new Person[1];
            obj2[0] = new Person();
            obj2[0] = new Student();
            
            Object[] obj3 = new String[1];
            obj3[0] = new Person(); 
        }
    }
    
    class Person {
    }
    
    class Student extends Person {
    }

在main方法的最后一行将会抛出一个异常

Exception in thread "main" java.lang.ArrayStoreException: cn.com.beansoft.box.Person
    at cn.com.beansoft.box.Box.main(Box.java:34)

在Collection的API中,声称toArray()与toArray(new Object[0])方法是等同的。但是Arrays.asList返回的List却没有这样的等同关系。其实在Arrays类中也有另外一个ArrayList类,只不过它是个内部类,Arrays.asList就是返回的这个内部的ArrayList。源码如下所示:

    public static  List asList(T... a) {
        return new ArrayList<>(a);
    }

而这个内部的ArrayList,其toArray方法是调用clone来实现的。如下:

   public Object[] toArray() {
        return a.clone();
   }

所以,如果Arrays.asList(T... array)里面的T不是Object类型,而是String类型。那么其toArray方法返回的只是一个String[]类型的数组,而不是Object[]类型。

java.util.ArrayList这个类,本来就是个泛型类。如果它底层的数组类型不是Object[]类型,那么实现泛型存储就是一件无法实现的事了。

上面所说的Java中数组是类型不兼容的,说白了就是在Java中我们可以这样做:

    Object o = new Person();
    o = new String();

但是不能这样做:

    Object[] obj3 = new String[1];
    obj3[0] = new Person();

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64425.html

相关文章

  • ArrayList源码解读(一)

    摘要:源码解读属性默认的初始化空间空的数组用于空对象初始化存储数组,非私有简化了嵌套类访问实际存储的数据量集合被操作次数,次数对不上抛出构造方法设置初始空间大小的构造方法大于就构造对应长度的数组等于就直接赋值空的数组对象小于就抛出异常无参构造方法 ArrayList源码解读 属性 private static final int DEFAULT_CAPACITY = 10;/...

    Meils 评论0 收藏0
  • JDK源码那些事儿之常用ArrayList

    摘要:前面已经讲解集合中的并且也对其中使用的红黑树结构做了对应的说明,这次就来看下简单一些的另一个集合类,也是日常经常使用到的,整体来说,算是比较好理解的集合了,一起来看下前言版本类定义继承了,实现了,提供对数组队列的增删改查操作实现接口,提供随 前面已经讲解集合中的HashMap并且也对其中使用的红黑树结构做了对应的说明,这次就来看下简单一些的另一个集合类,也是日常经常使用到的ArrayL...

    hizengzeng 评论0 收藏0
  • Java究极打基础之ArrayList

    摘要:将之前第位置的元素置空,返回被删除的元素。平常常用的迭代器方法就是判断当前索引是否等于。最重要的是会更新,此时调用了父类的方法,会使,所以更新了,让后续的检查不会抛异常。 本篇主要介绍ArrayList的用法和源码分析,基于jdk1.8,先从List接口开始。 List List接口定义了如下方法: int size(); boolean isEmpty(); boolean con...

    DevTalking 评论0 收藏0
  • Java笔记-容器源码(持续更新)

    摘要:加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行操作即重建内部数据结构,从而哈希表将具有大约两倍的桶数。成员变量每个对由封装,存在了对象数组中。 虽是读书笔记,但是如转载请注明出处 http://segmentfault.com/blog/exploring/ .. 拒绝伸手复制党 LinkedLis...

    mrli2016 评论0 收藏0
  • 数组和列表转换问题

    摘要:以下指代数组,指代数组列表。常见的转换方法是或。在的使用过程中需要注意,当要转换的长度小于的时,不要试图通过传入形参的方式进行转换,虽然这在的长度大于时不会出现问题。所以,极度建议在转换之前初始化的长度为的,并且使用返回值重新给赋值。 Array 和 List 都是我们在开发过程中常见的数据结构。我们都知道 Array 是定长的,List 是可变长。而且,List 的实现类 Array...

    ChristmasBoy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<