JavaSE基础之API-集合-手写MyArrayList

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

为了更好地理解ArrayList底层运行原理,特此模仿ArrayList集合,自己手动写一个简易版的MyArrayList。


一、思路

首先了解java中ArrayList的基本功能的书写特点,模仿它的底层容器数组、自动扩容功能和其他基本功能。

二、具体功能点如下

1、MyArrayList需要支持泛型,内部使用数组作为容器。

2、在MyArrayList中开发add方法,用于添加数据的,需要遵循ArrayList的扩容机制(自行设计代码,不需要与ArrayList的源代码一样,思想一致即可)

3、在MyArrayList中开发根据索引查询数据的get方法。

4、在MyArrayList中开发根据索引删除数据的remove方法。

5、在MyArrayList中开发一个获取集合大小的size()方法。

6、能够在MyArrayList集合中开发一个forEach方法,这个方法支持使用Lambda进行遍历,至于函数式接口叫什么名称无所谓。

7、编写测试用例对自己编写的MyArrayList集合进行功能正确性测试。

三.代码实现

MyArrayList类

public class MyArrayList<E> {
    //首先需要定义泛型,来给用户自定义数据类型

    //需要定义数组成员变量,因为这个数组的真实容量是等于用户获取的数组长度的,所以这里进行私有化
    private Object[] elementDate = {};
    //在Java中,由于泛型类型的类型擦除机制,无法在运行时创建泛型数组。因此,尝试创建泛型数组会导致编译器报错

    //定义数组长度size,用于给用户查询当前集合的长度,这个长度不应该被外界随意修改,所以进行私有化
    private int size;
    // 这里定义的数组的默认初始容量为10,是模范的java中ArrayList的写法
    private int DEFAULT_CAPACITY = 10;

    //add 添加方法
    public boolean add(E e) {
        //如果集合的长度等于数组的真实长度,这时候数组应该进行扩容操作
        if (size == elementDate.length) {
            grow();
        }
        //当第一次添加的时候,size初始值是0,所以将数据添加到索引0的位置,顺便将size向后移动一位,用来记录数组大小和添加下一个元素
        elementDate[size++] = e;
        return true;
    }

    //get 查询方法 根据索引查询数组元素
    public E get(int index) {
        //涉及到索引需要定义一个检查索引越界的方法
        checkIndex(index);
        return (E) elementDate[index];
    }

    public E remove(int index) {
        E e = (E) elementDate[index];
        //删除数据应该考虑是否需要进行元素移位操作。
        //0 1 2 3 4 5
        //0 1 2 4 5 0
        //这里使用size减1得到最后一个索引的位置再减去index就得到index后面的元素个数,也就是需要进行移动的元素个数,将它定义为移动标记
        int moveFlag = size - index - 1;
        //如果移动标记等于0,说明输入的索引是0数组也只有一个元素,这个时候不需要进行移位,直接跳过下面的if判断将数组的唯一元素赋值为空即可
        if (moveFlag != 0) {
            //如果移动标记不等于0,说明需要进行移位,这里使用的是System.arraycopy方法,参数依次为:原数组、原数组要复制的起始位置、
//            目标数组、目标数组的起始位置和要复制的元素个数
            System.arraycopy(elementDate, index + 1, elementDate, index, moveFlag);
        }
        //将数组最后一个元素赋空,并将数组长度-1
        elementDate[--size] = null;

        return e;
    }
    //forEach的遍历方法
    //因为forEach方法是基于迭代器实现的,而迭代器只能返回 Object 类型的元素。所以forEach方法会有一个接口函数的调用,函数的功能
    // 是将数组的每个值进行类型转换处理再返回的一个方法回调
    //这里我们模仿java中的Consumer 写一个MyConsumer来使用
    public void forEach(MyConsumer<E>action){
        //在遍历之前加一个判断传入的元素非空
        Objects.requireNonNull(action);
        for (int i = 0; i < size; i++) {
            action.accept((E) elementDate[i]);
        }

    }

    //返回集合长度
    public int size() {
        return size;
    }

    private void checkIndex(int index) {
        //应为size记录的永远是数组最后一个元素的下一个,所以索引等于size也会越界
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException(index + " out of max length " + size);
        }
    }

    private void grow() {
        //size等于0 说明是第一次添加数组,应该创建一个容量为10的初始数组
        if (size == 0) {
            elementDate = new Object[DEFAULT_CAPACITY];
        } else {
            //走到这里说明数组的容量不够用了,模仿java中ArrayList的写法应该将数组的容量扩大1.5倍
            //这里因为加法的优先级高于移位算法,所以后面用小括号括起来
            elementDate = Arrays.copyOf(elementDate, elementDate.length + (elementDate.length >> 1));
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < size; i++) {
            sb.append(i == size-1 ? elementDate[i] + "]": elementDate[i] + ", ");
        }

        return sb.toString();
    }
}

MyConsumer类

public interface MyConsumer<E> {
    abstract void accept(E e);
}

四.测试用例

测试类

public class MyArrayListTest {
    public static void main(String[] args) {
        MyArrayList<String>list = new MyArrayList<>();
        //查看集合初始长度
        System.out.println(list.size());
        //添加元素
        list.add("张三");
        list.add("李四");
        //查看数组第一次添加数据后的长度
        System.out.println(list.size());
        //打印数组
        System.out.println(list);
        //查找方法
        System.out.println(list.get(1));
        //删除方法
        System.out.println(list.remove(1));
        System.out.println(list);
        //forEach方法
        list.forEach(s -> System.out.println(s));
    }
}

总结

难点是删除方法 和 forEach方法,自己手动写一遍基本理解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值