你真的懂Java的ArrayList吗?

1. 为什么需要ArrayList?记得在刚刚学习JAVA的时候,我们首先是学习了数组,这是我们学到的第一个可以存储多个对象的实例或者基本类型的具体值,数组存储的特点如下:
1.只能存储同种类型的数据 。
2.在定义数组时,必须指定该数组的大小,并且在不改变数组的前提下,不可修改其长度 。
以上特性就会导致很多弊端 。比如:我们往往不希望数组只能存储一种数据,而是希望存储我们想要存储的数据,最好是在想要存储的时候根据数据的类型指定存储的类型 。其次,我们也不想一开始就指定好数据的长度,而是希望这个数组的容量可以随着我的数据的多少的改变而改变 。
基于以上的弊端,Java中出现了集合 。这是一种新的容器可以用来存储数据,而集合的存储方式有多种,常见的有链式存储(LinkedList)和顺序存储(ArrayList) 。
链式存储底层是用一个个节点(Node)链接而成的,每个节点都存储着一个对象值和下一个节点的位置(或上一个节点的位置) 。
顺序存储底层是用一个数组存储数据的,对于数组的弊端,顺序存储集合底层使用了ensureCapacity这个方法不断扩容,ensureCapacity这个单词字面翻译是 保证能力 。顾名思义,由于底层是一个数组,当我们存入一个对象时,我们需要保证数组是有空余位置的,因此在添加元素的时候,Java源码会先经过这个方法进行判断底层数组是否满了,若满了则会扩容数组(上面提到数组是不能直接扩容的,这里实际上是重新创建了一个更大空间的数组并把元素“搬运”过去) 。这样就解决了数组的一个弊端 。而对于另一个弊端,Java则是巧妙的运用了泛型 。泛型的内容非常繁多,这里结合实例希望大家可以更好地理解 。
想象一下现在有一个需求,需要你实现一个的多值加法,但传入的参数的类型是不确定的,可以有Integer,String,Double等等 。这时候如果你用的是数组作为参数,那么那你肯定会想,最粗糙的方法是分别写多个加法方法,对应不同的类型,但很明显,代码可读性极差,那如果使用Object数组,然后再根据数据类型,转换为对应的类型再计算?这样也存在弊端!你根本不知道需要转换为什么类型才合适 。因此,针对这种情况,使用泛型集合是最合适的,我们只需要在传入参数的时候使用泛型类型,而因为不同类型计算的过程是一致的,因此结果并没有差别,也不会导致报错 。
2. ArrayList底层是如何实现的?简单介绍了ArrayList的用途以及和数组的区别,那么根据上面的讲解,你应该大致了解它的实现原理了吧!
先不看源码,如果你有一些数据结构与算法的基础的话,你应该可以马上得出下面结论:先在ArrayList类定义一个数组,接着定义一个添加,一个删除,一个查询,一个修改方法 。实际上是对数组的操作,那么,删除和添加可能需要移动大量的元素,这些都是在源码中实现,但对应到效率也会很低,其次还需要一个扩容数组的方法 。
如果你能想到上面这些,恭喜你,你已经掌握得很不错 。事实上,ArrayList的源码确实包含以上方法,只不过还需要加上迭代器以及构造方法等 。迭代器的出现是为了适应增强for语句(后面会细说),构造方法是为了初始化集合 。
3. 结合源码分析主要成员变量ArrayList继承AbstractList这个抽象类和List接口

你真的懂Java的ArrayList吗?

文章插图
 
List接口继承Collection接口(实际上集合还有map集合等)
你真的懂Java的ArrayList吗?

文章插图
 
而Collection则是继承了Iterable(可迭代的),Collection中包含了集合中通用的方法,包括增删改查,只不过都未实现 。而Iterable则是只有一个forEach方法,提供迭代 。
你真的懂Java的ArrayList吗?

文章插图
 

你真的懂Java的ArrayList吗?

文章插图
 
接着我们回到ArrayList类,这是底层维护的数组,实际上对象存储的地方
你真的懂Java的ArrayList吗?

文章插图
 
记录集合的长度
你真的懂Java的ArrayList吗?

文章插图
 
返回集合的长度
 
你真的懂Java的ArrayList吗?

文章插图
 
判断集合是否为空
 


推荐阅读