用Java语言实现一个自定义的ArrayList类

简介: 自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。

以下是一个自定义的 MyArrayList 类实现,模拟了 Java 中 ArrayList 的核心功能,包括动态扩容、元素增删改查等操作,代码附带详细注释:

import java.util.Arrays;

/**
 * 自定义ArrayList实现,模拟动态数组功能
 * 支持泛型,实现基本的增删改查及扩容机制
 */
public class MyArrayList<E> {
   
    // 底层存储元素的数组(泛型擦除后为Object[])
    private Object[] elementData;
    // 当前元素数量(实际存储的元素个数)
    private int size;
    // 默认初始容量(无参构造器首次扩容时使用)
    private static final int DEFAULT_CAPACITY = 10;
    // 空数组(用于初始化为空的情况)
    private static final Object[] EMPTY_ARRAY = {
   };

    /**
     * 无参构造器:初始化为空数组,首次添加元素时扩容至默认容量10
     */
    public MyArrayList() {
   
        this.elementData = EMPTY_ARRAY;
    }

    /**
     * 有参构造器:指定初始容量
     * @param initialCapacity 初始容量(必须≥0)
     * @throws IllegalArgumentException 若初始容量为负数则抛出异常
     */
    public MyArrayList(int initialCapacity) {
   
        if (initialCapacity > 0) {
   
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
   
            this.elementData = EMPTY_ARRAY;
        } else {
   
            throw new IllegalArgumentException("初始容量不能为负数: " + initialCapacity);
        }
    }

    /**
     * 获取当前元素数量
     * @return 元素个数
     */
    public int size() {
   
        return size;
    }

    /**
     * 判断集合是否为空
     * @return 空返回true,否则返回false
     */
    public boolean isEmpty() {
   
        return size == 0;
    }

    /**
     * 向集合末尾添加元素
     * @param e 要添加的元素
     * @return 始终返回true(模拟ArrayList的add方法)
     */
    public boolean add(E e) {
   
        // 确保容量足够(不够则扩容)
        ensureCapacity(size + 1);
        // 存储元素并更新size
        elementData[size++] = e;
        return true;
    }

    /**
     * 在指定索引位置插入元素
     * @param index 插入位置(必须在0~size之间)
     * @param element 要插入的元素
     * @throws IndexOutOfBoundsException 若索引越界则抛出异常
     */
    public void add(int index, E element) {
   
        // 检查索引合法性(插入时索引可等于size,即末尾追加)
        checkIndexForAdd(index);
        // 确保容量足够
        ensureCapacity(size + 1);
        // 将index及之后的元素向后移动一位(复制数组)
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        // 插入新元素
        elementData[index] = element;
        size++;
    }

    /**
     * 获取指定索引的元素
     * @param index 索引位置
     * @return 该位置的元素
     * @throws IndexOutOfBoundsException 若索引越界则抛出异常
     */
    @SuppressWarnings("unchecked")
    public E get(int index) {
   
        checkIndex(index);
        return (E) elementData[index]; // 泛型强转(编译期unchecked警告)
    }

    /**
     * 修改指定索引的元素
     * @param index 索引位置
     * @param element 新元素
     * @return 被替换的旧元素
     * @throws IndexOutOfBoundsException 若索引越界则抛出异常
     */
    public E set(int index, E element) {
   
        checkIndex(index);
        E oldElement = get(index);
        elementData[index] = element;
        return oldElement;
    }

    /**
     * 删除指定索引的元素
     * @param index 索引位置
     * @return 被删除的元素
     * @throws IndexOutOfBoundsException 若索引越界则抛出异常
     */
    public E remove(int index) {
   
        checkIndex(index);
        E oldElement = get(index);
        // 计算需要移动的元素数量(index之后的元素个数)
        int moveCount = size - index - 1;
        if (moveCount > 0) {
   
            // 将index+1及之后的元素向前移动一位
            System.arraycopy(elementData, index + 1, elementData, index, moveCount);
        }
        // 释放最后一个元素的引用(帮助GC)
        elementData[--size] = null;
        return oldElement;
    }

    /**
     * 删除指定元素(仅删除第一个匹配项)
     * @param o 要删除的元素(可以为null)
     * @return 存在并删除返回true,否则返回false
     */
    public boolean remove(Object o) {
   
        if (o == null) {
   
            // 处理null元素(null只能用==判断)
            for (int i = 0; i < size; i++) {
   
                if (elementData[i] == null) {
   
                    fastRemove(i); // 内部快速删除(不重复检查索引)
                    return true;
                }
            }
        } else {
   
            // 处理非null元素(用equals判断)
            for (int i = 0; i < size; i++) {
   
                if (o.equals(elementData[i])) {
   
                    fastRemove(i);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 清空集合(释放所有元素引用)
     */
    public void clear() {
   
        // 遍历并置空所有元素(帮助GC回收)
        for (int i = 0; i < size; i++) {
   
            elementData[i] = null;
        }
        size = 0; // 重置元素数量
    }

    /**
     * 确保内部容量足够(核心扩容逻辑)
     * @param minCapacity 所需的最小容量
     */
    private void ensureCapacity(int minCapacity) {
   
        // 若当前数组为空(初始状态),则最小容量取默认容量和所需容量的最大值
        if (elementData == EMPTY_ARRAY) {
   
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 若所需容量超过当前数组长度,则触发扩容
        if (minCapacity > elementData.length) {
   
            grow(minCapacity);
        }
    }

    /**
     * 扩容核心方法:创建新数组并复制元素
     * @param minCapacity 所需的最小容量
     */
    private void grow(int minCapacity) {
   
        int oldCapacity = elementData.length;
        // 新容量 = 旧容量 + 旧容量/2(即1.5倍扩容,位运算效率更高)
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 若1.5倍扩容后仍不足,则直接使用所需最小容量
        if (newCapacity < minCapacity) {
   
            newCapacity = minCapacity;
        }
        // 复制原数组元素到新数组(新数组长度为newCapacity)
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    /**
     * 检查索引是否越界(用于get/set/remove等操作)
     * @param index 要检查的索引
     * @throws IndexOutOfBoundsException 若索引<0或≥size则抛出异常
     */
    private void checkIndex(int index) {
   
        if (index < 0 || index >= size) {
   
            throw new IndexOutOfBoundsException("索引越界:index=" + index + ", size=" + size);
        }
    }

    /**
     * 检查添加操作的索引是否越界(插入时索引可等于size)
     * @param index 要检查的索引
     * @throws IndexOutOfBoundsException 若索引<0或>size则抛出异常
     */
    private void checkIndexForAdd(int index) {
   
        if (index < 0 || index > size) {
   
            throw new IndexOutOfBoundsException("索引越界:index=" + index + ", size=" + size);
        }
    }

    /**
     * 快速删除(内部使用,跳过索引检查以提高效率)
     * @param index 要删除的索引
     */
    private void fastRemove(int index) {
   
        int moveCount = size - index - 1;
        if (moveCount > 0) {
   
            System.arraycopy(elementData, index + 1, elementData, index, moveCount);
        }
        elementData[--size] = null;
    }

    /**
     * 重写toString,方便打印集合内容
     * @return 集合的字符串表示(格式:[e1, e2, ..., en])
     */
    @Override
    public String toString() {
   
        if (size == 0) {
   
            return "[]";
        }
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < size; i++) {
   
            sb.append(elementData[i]);
            if (i != size - 1) {
   
                sb.append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }
}

核心功能说明

  1. 底层结构:使用 Object[] 数组存储元素,通过泛型支持任意数据类型(利用类型擦除实现,获取元素时需强转)。

  2. 容量管理

    • 无参构造器初始化为空数组,首次添加元素时扩容至默认容量 10
    • 有参构造器可指定初始容量,避免频繁扩容。
    • 扩容规则:当元素数量超过当前容量时,新容量为旧容量的 1.5 倍(通过 oldCapacity + (oldCapacity >> 1) 计算);若 1.5 倍仍不足,则直接使用所需最小容量。
  3. 核心方法

    • 新增:add(E e)(尾插)、add(int index, E element)(指定位置插入,需移动后续元素)。
    • 查询:get(int index)(获取指定位置元素)。
    • 修改:set(int index, E element)(替换指定位置元素)。
    • 删除:remove(int index)(按索引删除)、remove(Object o)(按元素删除,支持 null)。
    • 工具:size()(元素数量)、isEmpty()(是否为空)、clear()(清空集合)。
  4. 异常处理:对越界索引抛出 IndexOutOfBoundsException,对负数初始容量抛出 IllegalArgumentException

  5. 内存优化:删除元素后主动将末尾元素置为 null,帮助垃圾回收(GC)释放内存。

使用示例

public class TestMyArrayList {
   
    public static void main(String[] args) {
   
        MyArrayList<String> list = new MyArrayList<>();

        // 添加元素
        list.add("Java");
        list.add("Python");
        list.add(1, "C++"); // 在索引1插入元素
        System.out.println(list); // 输出: [Java, C++, Python]

        // 修改元素
        list.set(2, "JavaScript");
        System.out.println(list.get(2)); // 输出: JavaScript

        // 删除元素
        list.remove(0);
        System.out.println(list); // 输出: [C++, JavaScript]

        list.remove("JavaScript");
        System.out.println(list.size()); // 输出: 1

        // 清空集合
        list.clear();
        System.out.println(list.isEmpty()); // 输出: true
    }
}

该实现模拟了 JDK ArrayList 的核心逻辑,适合理解动态数组的底层原理。实际开发中建议使用 JDK 自带的 ArrayList(经过高度优化,支持更多功能如迭代器、序列化等)。

目录
相关文章
|
3月前
|
安全 Java 数据建模
Java记录类:简化数据载体的新选择
Java记录类:简化数据载体的新选择
268 101
|
2月前
|
Java
Java语言实现字母大小写转换的方法
Java提供了多种灵活的方法来处理字符串中的字母大小写转换。根据具体需求,可以选择适合的方法来实现。在大多数情况下,使用 String类或 Character类的方法已经足够。但是,在需要更复杂的逻辑或处理非常规字符集时,可以通过字符流或手动遍历字符串来实现更精细的控制。
282 18
|
2月前
|
IDE JavaScript Java
在Java 11中,如何处理被弃用的类或接口?
在Java 11中,如何处理被弃用的类或接口?
214 5
|
2月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
199 1
|
2月前
|
Java Go 开发工具
【Java】(8)正则表达式的使用与常用类分享
正则表达式定义了字符串的模式。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
256 1
|
2月前
|
存储 Java 程序员
【Java】(6)全方面带你了解Java里的日期与时间内容,介绍 Calendar、GregorianCalendar、Date类
java.util 包提供了 Date 类来封装当前的日期和时间。Date 类提供两个构造函数来实例化 Date 对象。第一个构造函数使用当前日期和时间来初始化对象。Date( )第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。
205 0
|
2月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
226 1
|
2月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
323 5
|
存储 Java 索引
【Java集合类面试二十四】、ArrayList和LinkedList有什么区别?
ArrayList基于动态数组实现,支持快速随机访问;LinkedList基于双向链表实现,插入和删除操作更高效,但占用更多内存。
|
存储 Java 索引
Java 中 ArrayList 和 LinkedList 之间的区别
【8月更文挑战第22天】
335 1