如何撸一个ArrayList

起因

前段时间在看一本书码农翻身。这本书很不错,推荐阅读!(真不是打广告,好书要分享)。老实说,在此之前,这是一个公众号(好吧,现在也是),里面写了很多通俗易懂的文章,没想到最近居然出书了,赶快买了一本,一通畅读之后,在书中的其中一章看到了一些有意思的事情。

大意就是,他写了一个List的接口,里面有add()方法,size()方法等等一系列ArrayList的常用方法,然后让面试者去实现这个类,也就是让我们实现ArrayList啊,然后书中还说,他面试了很多人,但是能够实现的不错的人没多少。

接受挑战!

可能实现的很水,也许代码质量远不如各种大神们实现的好,但是至少也实现了不是吗,老实说,在实现完毕后,对ArrayList好像了解的也更深刻了,不过没有细细研读Java自身实现的ArrayList,因为我考虑的只有实现,并没有为了以后要拓展这个类中的功能预留一些的接口或者什么= =。

开撸

首先我们先来撸一个接口,为了不和Java里面的List接口重名,就叫IList吧,思考了一下常用功能,最终长这样:

public interface IList<T> {
    void add(T t);
    void addAll(IList<T> tiList);
    void remove(int i);
    T get(int index);
    int size();
    void clear();
    boolean constain(T t);
}

功能就这些,也不打算多写,相信你们看方法名字也知道这里每个方法是干啥的吧。不过跟Java实现的List接口差别好像还不是一星半点- -!

分析

好吧,现在的问题就是,拿什么来存储我们的数据,跟集合类似的,也就只有数组最合适了,所以自然而然的,Object[]成为了我们实现ArrayList的得力助手,但是数组的长度是固定的啊,这数组~,看来只好每次要增删数据的时候,对数组的长度更新了。

所以,IList中的每个方法的具体实现思路就是这样的:

  1. void add(T t);
    实现方式:克隆一份原数组,新数组容量+1,将原数组的值一一赋值到新数组中,将要添加的值,放到新数组的末尾

  2. void addAll(IList<T> tiList);
    实现方式:跟add(T t)类似,克隆一份原数组,新数组容量+tiList.size()个大小,将原数组的值一一赋值到新数组中,将要添加的值,放到新数组多加的位置上

  3. void remove(int i);
    实现方式:克隆一份原数组,新数组容量-1,将原数组中的值一一赋值到新数组中,当添加到第i个的时候,做一下判断,少添加要被去除的值

  4. T get(int index);
    实现方式:直接返回数组对应的值

  5. int size();
    实现方式:返回数组长度

  6. void clear();
    实现方式:清空数组

  7. boolean constain(T t);
    实现方式:遍历一遍数组,查看是否包含t

所以整个代码的样子就是这样的:

/**
 * 模仿ArrayList
 * @param <T> 可以添加的元素
 */
public class SimpleList<T> implements IList<T> {

    /**
     * 用来记录数据的数组
     */
    private Object[] es;

    /**
     * 默认内容(空)
     */
    private Object[] defaultArr = {};

    public SimpleList() {
        es = defaultArr;
    }

    /**
     * 新增元素
     *
     * @param t 被增加的元素
     */
    @Override
    public void add(T t) {
        es = expandArr(1);
        es[es.length - 1] = t;
    }

    /**
     * 将指定集合中的元素全部添加到自己身上
     *
     * @param list
     */
    @Override
    public void addAll(IList<T> list) {

        int ori = es.length;
        es = expandArr(list.size());

        for (int i = ori; i < es.length; i++) {
            es[i] = list.get(i - ori);
        }

    }

    /**
     * 移除指定位置的元素
     *
     * @param i 位置
     */
    @Override
    public void remove(int i) {

        if (i < size()) {
            Object[] clone = es.clone();
            es = new Object[clone.length - 1];
            boolean isJump = false;
            for (int j = 0; j < clone.length; j++) {
                if (j == i) {
                    isJump = true;
                    continue;
                }
                if (isJump) {
                    es[j - 1] = clone[j];
                } else {
                    es[j] = clone[j];
                }
            }
        } else {
            throw new RuntimeException("ArrlistOutOfIndex exception: size = " + size() + ", index = " + i);
        }

    }

    /**
     * 获取某个元素
     *
     * @param index 指定位置
     * @return 具体元素
     */
    @Override
    public T get(int index) {
        if (index < size()) {
            return (T) es[index];
        }
        throw new RuntimeException("outOfIndexException: index = " + index + ", size = " + size());
    }

    /**
     * 得到集合元素的数量
     *
     * @return 元素的数量
     */
    @Override
    public int size() {
        return es.length;
    }

    /**
     * 清空所有元素
     */
    @Override
    public void clear() {
        es = defaultArr;
    }

    /**
     * 判断本集合是否存在该集合
     *
     * @param o
     * @return
     */
    @Override
    public boolean constain(Object o) {
        for (int i = 0; i < size(); i++) {
            if (es[i].equals(o)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 在原数组基础上,"扩大"数组的大小
     *
     * @param count 扩大的尺寸
     * @return 已经扩大的数组,并且该数组上有自己的元素
     */
    private Object[] expandArr(int count) {
        Object[] clone = es.clone();
        es = new Object[es.length + count];
        for (int i = 0; i < clone.length; i++) {
            es[i] = clone[i];
        }
        return es;
    }

}

测试

既然都实现好了,我们不妨来试试?

public class Main {

    public static void main(String[] args) {

        // 新增数据
        SimpleList<String> simpleList1 = new SimpleList<>();
        simpleList1.add("Str1");
        simpleList1.add("Str2");
        simpleList1.add("Str3");

        show("simpleList1", simpleList1);
        System.out.println("simpleList1.size = " + simpleList1.size());

        // 移除数据
        System.out.println("移除第index为2的数据(从0开始,所以实际移除的是第三个数据)");
        simpleList1.remove(2);
        show("移除后的simpleList1", simpleList1);

        // 批量新增数据
        SimpleList<String> simpleList2 = new SimpleList<>();
        simpleList2.add("a1");
        simpleList2.add("a2");
        simpleList2.add("a3");
        simpleList2.addAll(simpleList1);

        show("simpleList2", simpleList2);

        // 是否包含某数据
        System.out.println("simpleList2是否包含a2:" + simpleList2.constain("a2"));
        System.out.println("simpleList2是否包含a9:" + simpleList2.constain("a9"));

        // 清空数据
        simpleList2.clear();
        System.out.println("清理后:simpleList2.size() = " + simpleList2.size());

    }

    private static void show(String name, SimpleList<String> list) {
        System.out.print(name + ":");
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
        System.out.println();
    }

}

运行结果:

simpleList1:Str1 Str2 Str3 
simpleList1.size = 3
移除第index2的数据(从0开始,所以实际移除的是第三个数据)
移除后的simpleList1:Str1 Str2 
simpleList2:a1 a2 a3 Str1 Str2 
simpleList2是否包含a2:true
simpleList2是否包含a9:false
清理后:simpleList2.size() = 0

毫无问题,这里我要提一个单词:Nice!

最后

其实自己手动实现一个ArrayList也挺有趣的,ArrayList的结构和数组类似,仿佛是在排列成一排的空格子里面放东西,是不是有点像打麻将的时候,大家面前的麻将。麻将都是排成一排的,可以随时从中间抽一个麻将打出去,也可以拿一个麻将进来(好像没有实现指定插入位置,这个问题就交给你们啦!),话说发明ArrayList的人,是不是打麻将的时候找到的灵感= =。

猜你喜欢

转载自blog.csdn.net/IT_XF/article/details/81565213