手写集合框架ArrayList篇
ArrayList
Hello,头发多的我又来了,刚利用业余时间,实现了一个简易版的ArrayList,下面我来分享一下我的实践内容。由于水平有限,咱们尽量都用白fa(话)文
一、思路
首先,我的思路是这样的,因为ArrayList
是List
接口下的,所以我在接口中定义了我要实现的抽象方法,以便接下来将这些方法给ArrayList
重写,而且,因为到时候LinkedList
也会要用到这些公用的方法名,所以写了List
接口以供实现。
二、定义主要方法
/**
* 提供的方法 让arraylist继承 , 并重写这些方法
* @author Administrator
*
*/
public interface newList{
public void add(); //集合的添加方法
public int size(); //获取集合的长度
public Object get(Integer i ); //获取下标的为i的元素
public void remove(Object object); //移除object对象
}
几个主要的方法,add,remove,size,以及get
。这里就没有去实现指定位置添加的方法,相信这几个弄懂了,其他的不在话下,稍加看看源码即可
集合接口有了,我们照着思路往下走,让ArrayList
去实现它,并重写它所有的方法就OK了。
基本的结构就这样了,废话不多说,直接开始我们ArrayList
的实现
三、定义全局属性
//一个object类型的数组
private Object[] array;
private int size ; //当前集合的长度
private static final int DEFAULT_SIZE = 10 ; //数组的默认长度
通过源码,我们了解到,ArrayList
是通过数组实现的,而且是一个Object
类型的,为什么是Object
,也是为了符合不同数据类型的数组存储,毕竟Object
是类型老大
定义完数组,是不是发现它并没有默认长度,所以我们在下面定义了一个默认的常量DEFAULT_SIZE=10
,这个就是它默认的长度,这个时候可能会有一个疑惑,就是这个size
是干莫子的,你是否联想到我们的size()
方法【获取数组ArrayList的长度】,所以这里的size
全局属性就是用来控制整个ArrayList
集合的内容长度的,并非是我们的数组长度,也就是size!=数组长度
,它等于的是ArrayList
这个集合的长度。
四、定义构造函数
方法有了,接下来就是也是比较重要的一步骤,就是ArrayList
的构造函数
//定义一个构造器 用来实例该类
public newArrayList() {
array = new Object[DEFAULT_SIZE]; //初始化一个数组
size = 0 ;
}
public newArrayList(int resetsize){
if(resetsize>0) { //指定长度的初始数组
array = new Object[resetsize];
}else if(resetsize == 0) { //如果等于0 使用默认的长度DEFAULT_SIZE
array = new Object[DEFAULT_SIZE];
}else { //不合法的长度 比如-1
throw new IllegalArgumentException("指定了错误的长度:"+resetsize);
}
}
为什么要有多种的不同的构造函数呢?很-单嘛,根据你平常的使用的习惯,你可能不想给集合长度,或者给它一个长度,这不就涉及到了两个不同的构造函数嘛,所以我们也定义两个,无参构造为了满足不给长度的情况下,不给指定长度我们就使用我们之前定义的DEFAULT_SIZE
这个常量,如果指令了默认长度我们就使用第二个带int
参数的构造函数,并且在里面判断,判断这个整型数字是否满足我们的条件。
五、实现主要的方法
1. add()方法 集合添加
// 进行 判断 如果 数组溢出都 扩容
@Override
public void add(Object object) {
if(size == array.length) { //达到扩容的条件
this.ensureCapacity();
}
array[size] = object; //赋值 并累计size
size ++ ;
}
//扩容的方法
private void ensureCapacity() {
//创建一个新的数组 是原来的2倍
Object[] newarray = new Object[array.length*2];
System.arraycopy(array, 0, newarray,0,array.length);
array = newarray; //重新赋值
}
我们数组,它固然是由长度限制的,所以我们应该在哪里进行扩容呢?放add()
这个方法里面是最合理的也是最恰当的,因为只有我们数据添加的时候,集合的长度才会越来越长嘛,所以我们在add()
方法这里加了一个等于的判断,当它符合扩容条件,也就是已经和数组长度相等时,就对这个数组扩容,扩容这里我们单独列出了一个方法,写了一个新的数组newarray
,它的长度是原来数组长度的二倍,当然源码当中不是这么定义的,我们是为了方便理解,然后将这个数组新的数组复制到我们原来的数组当中即可。这里需要普及一个System的方法System.arrarcopy(参数1,参数2,参数3,参数4,参数5);
他们分别代表的含义如下:
参数1:原数组,
参数2:原数组的起始位置,
参数3:目标数组,
参数4:目标数组的起始位置,
参数5:需要复制的数组长度
每调用一次add()
方法,我们的size
也就自增一次,代表集合的长度正在发生变化
2. get()方法 获取集合指定下标的元素
//获取指定下标的元素
@Override
public Object get(Integer i) {
if (i<0 ||i>size-1) { //数组溢出异常
throw new IndexOutOfBoundsException();
}
return array[i];
}
我们知道数组是自带索引的,从0
开始,所以我们获取的时候只需要判断一下给定的长度i
是否符合条件,不符合条件的情况就抛出数组溢出的异常,否则就返回这个数组中这个下标的的数组内容出去。
3. remove()方法 获取集合指定下标的元素
//删除集合指定的元素
@Override
public void remove(Object object) {
for (int i = 0; i < array.length; i++) {
if(object == array[i]) { //如果内容相等 1.元素上移 2. 删除原元素
// 拿到需要移动的个数
int index = size - i - 1 ;
System.arraycopy(array,i+1 , array,i,index); //前移
array[--size] = null; //最后一个空值 也就登录删除了一个下标
/* 假设集合总长度size = 8,然后我删除第5个,也就是下标 i = 4这个元素
这个时候要右边要移动的元素个数也就是index = 8 - 4 - 1
;也就是有3个元素要移动
然后再熟练使用我们刚刚的System.arraycopy这个方法,对这个数组进行操作
,我们可以知道我们的数组起始位置分别是i+1以及i,也就是从我们当前这个/
下标为4+1这个位置开始,分别向前移动一个位置,将第五个元素的内容移动/
到第四个,第六个移动到第五个的位置,
依次类推,最后达到补齐我们要删除的这个空缺。
最后我们移动完之后,最后一个元素肯定会是空缺的,所以我们将它赋空值,并
且size-1(集合长度变化) 这个时候才算完成。
*/
}
}
}
这里我们只做了,根据内容进行删除,传一个内容进来,然后循环整个数组去判断,如果找到了这个内容我们就要把这个内容去除掉,这里可能会有一丢丢疑惑,因为我也看了好一会,这里要做的一个操作就是,我们要对这个数组进行移动,解释已经在注释当中标识。还请客观仔细品,为了方便理解,附上一张方向图。
4. size()方法 获取集合的长度
//获取数组的长度
@Override
public int size() {
return size;
}
这个方法应该比较容易理解了把,不过还要强调的一点是 size != 数组长度
,它是集合的长度,O不OK?
基本方法就这么结束了。
六、迭代器部分
等等,不要急不要躁。还记得我们上次我们提的迭代器
嘛?OK,下面我们就在上面我们的基础上加一个迭代器
。还记得迭代器
是干嘛的嘛,迭代器可以用来循环我们这个集合,并且在循环的过程中可以对当前循环对象进行删除操作,有时候面试官就问你,怎么一边循环稽核一边删除,迭代器迭代器迭代器!
迭代器首先也是要一个接口
/**
* 迭代器的接口
* @author Administrator
*
*/
public interface newIterator {
boolean hasNext(); //判断是否有下一个元素的方法
Object Next(); //获取元素
void remove(); //移除元素 和arraylist的指定移除remove(Object object)几乎一样
}
接口定义好勒,我们继续在List
这个接口下写上我们的获取迭代器
的方法,让ArrayList
重写
public newIterator getIterator();
ArrayList
当中需要这样写
//返回新的迭代器
public newIterator getIterator() {
return new newArrayListIterator();
}
沃特???疑问来了,返回的这个 newArrayListIterator
在哪???别急嘛,继续看咯
//实现迭代器接口
private class newArrayListIterator implements newIterator{
private int index =0; //用来判断是否有下一个
private int lastindex =-1; //用来记住上一次的位置,可以理解为当前循环的数组下标
@Override
public boolean hasNext() {
return index!=size;
}
@Override
public Object Next() {
lastindex = index;
return array[index++];
}
@Override
public void remove() {
if(lastindex < 0) { //如果没有元素 就抛出异常
throw new IllegalSelectorException();
}
int moveindex = size - lastindex -1 ;
System.arraycopy(array,lastindex+1, array, lastindex, moveindex);
array[--size] = null;
index --;
lastindex = -1 ;
}
}
这个私有的内部类
同样在ArrayList
当中去实现,index
用来作比较,lastindex
是我们上一次的位置,如果你不太理解,建议你先学会如何使用迭代器
在看这部分。
其中:
1.hasNext() 判断条件
hasNext()
这个方法,是在while循环
中,用来判断true||false的条件
,只有满足index!=size
,也就是,当前循环对象不是最后一个,这个循环才能继续下去。
2.Next() 获取当前循环的元素内容
Next()
方法,就是用来获取当前循环对象的内容的,返回一个index++
下标的内容,这里不要将index
比如是6,index++
不要理解为这里是返回下标为7的内容,区分index++
和++index
的区别,所以如果这里index = 6
,他返回的也还是下标为6的内容
3.remove() 删除当前循环对象
这里不作太多重复的阐述,因为这里和我们之前的remove(Object object)
原理类似。
七、泛型部分
最后在做最后一步的优化,给List
集合加上泛型
public interface newList<T> { //定义泛型,因为Object可以存储任意类型,有时候我们需要
//用泛型 代替Object
public void add(T t); //集合的添加方法
同理,remove修改一下参数 变为泛型代表--T
使用泛型有一个好处,就是有些时候我们的在转换的过程中不需要强制转换
举个例子:
我ArrayList
中两个这样的方法
@Override
public Object get2(Object object) {
// TODO Auto-generated method stub
return object;
}
@Override
public <T> T get3(T t) {
// TODO Auto-generated method stub
return t;
}
一个是返回Object
,一个返回泛型
结果如下:
newList<Object> li2 = new newArrayList<Object>();
Object ob = new Object();
Object o = (newList<Object>) li2.get2(ob);
Object o2 = li2.get3(ob);
你就会发现,使用Object
是不是要强转,而使用泛型因为我最开始就指定了集合 T = Object
所以不再需要强转啦 奥利给!!!
终于要写完了,最后我们在做个小小的总结,以及解答一些面试中小小的疑惑。
到此为止我们要知道的是:
1.ArrayList是List接口下一个有序的集合
2.ArrayList通常用于查询功能,
答:我们查询出来的数据之后,调用add方法,add方法总是追加到最后一个元素后面,这里几乎没有什么多余的线性时间,当然你可能会说数组扩容消耗了这么大的性能,但你要知道ArrayList最突出的是获取的时候,直接用get()方法通过索引去获取元素,这样速度是非常快的,后续我们可以拿它个LinkedList作一个对比。
3.另外像一些集合克隆,就自己去琢磨把,思路(直接给ArrayList一个克隆方法返回整个集合或者加一个构造函数返回这个集合)
遇见您,是我们之间的缘分,三生有幸! \
- WeChat:lljb1218