手写集合框架ArrayList篇

ArrayList

Hello,头发多的我又来了,刚利用业余时间,实现了一个简易版的ArrayList,下面我来分享一下我的实践内容。由于水平有限,咱们尽量都用白fa(话)文

一、思路

首先,我的思路是这样的,因为ArrayListList接口下的,所以我在接口中定义了我要实现的抽象方法,以便接下来将这些方法给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集合加上泛型
java 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

猜你喜欢

转载自www.cnblogs.com/linjiab/p/12031547.html