滚雪球学Java(58):玩转Java中的ArrayList:常用操作技巧和方法总结

简介: 【6月更文挑战第12天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

在这里插入图片描述

  咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~

在这里插入图片描述


🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!

环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

@[toc]

前言

  在Java开发中,ArrayList是一种常见的数据结构。它可以动态地增加或删除元素,十分方便。但是,要想熟练掌握ArrayList的使用,不仅要了解常用的操作技巧和方法,还需要深入理解它的实现原理。本文就旨在帮助读者更好地掌握ArrayList,快速地进行开发。

摘要

  本文首先介绍了ArrayList的定义和特点,然后详细描述了它的常用操作技巧和方法,包括添加元素、删除元素、遍历元素、查找元素等。接下来,通过源代码解析让读者了解ArrayList的底层实现原理,进一步理解它的性能和使用限制。进一步,本文通过应用场景案例分析,探讨ArrayList的使用场景和优缺点,以帮助读者在实际开发中更好地选择数据结构。最后,通过测试用例和全文小结,对本文内容进行总结,让读者更好地掌握ArrayList的使用方法和技巧。

ArrayList

概述

  ArrayList是Java中的一个类,它继承了AbstractList类,并且实现了List接口。它是一个动态数组,数组长度可以动态增加和缩小,它可以对数组中的元素进行增、删、改、查等操作。ArrayList可以存储任意类型的对象。

特点

ArrayList的特点包括:

  1. 可变长度:ArrayList的长度是可变的,可以动态增加或缩小。
  2. 重复元素:ArrayList中可以存储重复的元素。
  3. 线程不安全:ArrayList不是线程安全的,如果有多个线程同时访问同一个ArrayList实例,可能会出现竞争条件。
  4. 非同步:ArrayList不是同步的,不保证多个线程并发访问时的安全性。
  5. 查找元素效率高:通过索引可以快速查找元素,时间复杂度为O(1)。

源代码解析

  ArrayList的底层实现是基于数组的,具体来说,它是通过一个Object类型的数组来存储元素的。在第一次添加元素时,会创建一个默认长度为10的数组,当数组空间不足时,会创建一个长度为原来数组长度+原来数组长度/2的新数组,并将原来数组中的元素复制到新数组中。在删除元素时,会将该元素后面的所有元素前移,如果删除的元素位于数组中间,则需要复制这两部分,同时更新数组长度。

  ArrayList是Java集合框架中的一种数据结构,它基于数组实现,动态增长。下面是ArrayList的源代码解析:

  1. 类型参数定义:
public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
   
   
    ...
}

  可以看到ArrayList实现了List接口、RandomAccess接口以及Cloneable接口,并且继承了AbstractList抽象类。其中,E表示ArrayList中的元素类型。

  如下是部分源码截图:

在这里插入图片描述

  1. 成员变量定义:
// 默认容量为10
private static final int DEFAULT_CAPACITY = 10;

// 所有元素
transient Object[] elementData;

// 数组实际数量
private int size;

// 可选的List迭代器
private static final class ListItr<E> extends Itr<E> implements ListIterator<E> {
   
   
    ...
}

  elementData数组存放ArrayList中的所有元素,size记录了当前存放元素的数量。

在这里插入图片描述

  1. 构造函数定义:
// 无参构造函数,初始化为默认容量10
public ArrayList() {
   
   
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 带初始容量参数的构造函数
public ArrayList(int initialCapacity) {
   
   
    if (initialCapacity > 0) {
   
   
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
   
   
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
   
   
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

// 带集合参数的构造函数
public ArrayList(Collection<? extends E> c) {
   
   
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
   
   
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
   
   
        // 若集合为空,则初始化为默认容量
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

  ArrayList支持无参、带初始容量参数、带集合参数的构造函数。其中,无参构造函数初始化为默认容量10,带初始容量参数的构造函数会检查参数是否合法,如果小于等于0则抛出IllegalArgumentException异常,带集合参数的构造函数会将集合转化为数组并存放在elementData数组中。

在这里插入图片描述

  1. 常用方法定义:
// 添加元素,支持链式调用
public boolean add(E e) {
   
   
    // 确保elementData数组可以存放新元素
    ensureCapacityInternal(size + 1); 
    elementData[size++] = e;
    return true;
}

// 获取指定下标的元素
@SuppressWarnings("unchecked")
public E get(int index) {
   
   
    rangeCheck(index);
    return (E) elementData[index];
}

// 删除指定下标的元素
public E remove(int index) {
   
   
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; 

    return oldValue;
}

// 获取元素数量
public int size() {
   
   
    return size;
}

  其中,ensureCapacityInternal方法用于确保elementData数组容量足够存放新元素,rangeCheck方法用于检查下标是否越界,modCount记录集合结构修改次数。

  1. 扩容机制

  在添加元素时,如果elementData数组容量不足,则需要扩容。扩容过程如下:

private void grow(int minCapacity) {
   
   
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 将原数组元素拷贝到新数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private void ensureCapacityInternal(int minCapacity) {
   
   
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
   
   
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private static int hugeCapacity(int minCapacity) {
   
   
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
   
   
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
   
   
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

  grow方法用于扩容,将原数组容量增加一半并重新分配大小,如果新容量小于minCapacity,则将minCapacity赋值给新容量。如果新容量大于MAX_ARRAY_SIZE,则将新容量赋值为MAX_ARRAY_SIZE。然后将原数组元素拷贝到新数组中。ensureCapacityInternal方法用于确保elementData数组容量足够存放新元素。ensureExplicitCapacity方法用于确保elementData数组容量不小于minCapacity。calculateCapacity方法计算出需要的最小容量,如果elementData数组为空,则返回默认容量DEFAULT_CAPACITY和minCapacity中的较大值。

常用操作技巧和方法

添加元素

  在ArrayList中添加元素最基本的方法就是add()方法,该方法有两种重载形式,一种是无参的add()方法,一种是有参数的add(int index, E element)方法。无参的add()方法会在ArrayList的最后一位添加一个元素,而有参数的add(int index, E element)方法则可以将元素插入到指定的索引位置。

public boolean add(E e)
public void add(int index, E element)

  如下是部分源码截图:

在这里插入图片描述

删除元素

  在ArrayList中删除元素最常用的方法是remove()方法,该方法也有两种重载形式,一种是删除指定索引位置的元素,另一种是删除指定元素。需要注意的是,如果是使用remove(int index)方法删除元素,则会将该位置后面的所有元素向前移动一位,而如果是使用remove(Object o)方法,则会将第一个匹配到的元素删除。

public E remove(int index)
public boolean remove(Object o)

遍历元素

在ArrayList中遍历元素可以使用for循环和foreach语句,如下所示:

package com.example.javase.se.classes;

import java.util.ArrayList;

/**
 * @Author ms
 * @Date 2023-11-02 19:13
 */
public class ArrayListTest {
   
   

    public static void main(String[] args) {
   
   
        ArrayList<String> list = new ArrayList<String>();
        list.add("Java");
        list.add("Python");
        list.add("C++");

        // 使用for循环遍历元素
        for (int i = 0; i < list.size(); i++) {
   
   
            String element = list.get(i);
            System.out.println(element);
        }

        // 使用foreach语句遍历元素
        for (String element : list) {
   
   
            System.out.println(element);
        }
    }

}

测试结果

  根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。

在这里插入图片描述

查找元素

  在ArrayList中查找元素可以使用indexOf()方法或contains()方法。其中,indexOf()方法返回该元素第一次出现的位置的索引值,如果没有找到该元素,则返回-1;contains()方法返回ArrayList中是否存在该元素,返回值为布尔型。

public int indexOf(Object o)
public boolean contains(Object o)

应用场景案例

场景一:存储简单数据类型

  在Java中,数组是一种非常基本的数据结构,可以用来存储简单数据类型,例如int、double、boolean等。但是,数组的长度是不可变的,如果需要动态增加或删除元素,就需要使用ArrayList。例如:

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);

场景二:实现栈和队列

  栈和队列是计算机科学中常见的数据结构。栈是一种LIFO(Last-In-First-Out)的数据结构,队列是一种FIFO(First-In-First-Out)的数据结构。借助ArrayList可以很容易地实现栈和队列。例如:

// 实现栈
ArrayList<Integer> stack = new ArrayList<Integer>();
stack.add(1);
stack.add(2);
stack.add(3);
Integer top = stack.remove(stack.size() - 1);

// 实现队列
ArrayList<Integer> queue = new ArrayList<Integer>();
queue.add(1);
queue.add(2);
queue.add(3);
Integer front = queue.remove(0);

优缺点分析

ArrayList的优点包括:

  1. 索引快速查找:通过索引可以快速查找元素,时间复杂度为O(1)。
  2. 可变长度:ArrayList的长度是可变的,可以动态增加或缩小。
  3. 可存储任意类型的对象:ArrayList可以存储任意类型的对象,灵活性高。

ArrayList的缺点包括:

  1. 不适合大量元素的添加和删除:由于ArrayList底层是基于数组实现的,因此在进行大量元素的添加或删除时,会存在数组复制的问题,影响性能。
  2. 线程不安全:ArrayList不是线程安全的,如果有多个线程同时访问同一个ArrayList实例,可能会出现竞争条件。
  3. 非同步:ArrayList不是同步的,不保证多个线程并发访问时的安全性。

类代码方法介绍

  在Java中,ArrayList是一个类,我们可以自己创建ArrayList对象并进行操作。ArrayList中的常用方法已经在前面进行了介绍,这里就不再赘述,但是需要注意的是,ArrayList继承了AbstractList类,并且实现了List接口,因此,它还有一些其他的方法,例如:

public boolean isEmpty()
public void clear()
public boolean equals(Object o)
public Object[] toArray()

测试用例

  在本文中,我们介绍了ArrayList的定义和特点,详细描述了它的常用操作技巧和方法,通过源代码解析让读者了解ArrayList的底层实现原理,通过应用场景案例分析,探讨ArrayList的使用场景和优缺点,以帮助读者在实际开发中更好地选择数据结构。下面,我们构建一个测试用例来检验ArrayList的操作是否正确:

package com.demo.javase.day58;

import java.util.ArrayList;

/**
 * @Author bug菌
 * @Date 2023-11-05 23:45
 */
public class ArrayListTest {
   
   

        public static void main(String[] args) {
   
   
            // 创建一个空的ArrayList对象
            ArrayList<String> list = new ArrayList<String>();

            // 添加元素
            list.add("Java");
            list.add("Python");
            list.add("C++");

            // 遍历元素
            for (String element : list) {
   
   
                System.out.println(element);
            }

            // 插入元素
            list.add(1, "JavaScript");

            // 删除元素
            list.remove("C++");

            // 查找元素
            boolean contains = list.contains("Java");
            int index = list.indexOf("Python");

            // 输出结果
            System.out.println(list);
            System.out.println("List contains Java? " + contains);
            System.out.println("Index of Python: " + index);
        }
    }

运行结果如下所示:

Java
Python
C++
[Java, JavaScript, Python]
List contains Java? true
Index of Python: 2

测试结果

  根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。

在这里插入图片描述

  测试用例中,我们创建了一个空的ArrayList对象,并向其中添加了三个元素。接着,我们使用for-each语句遍历ArrayList中的所有元素,然后插入了一个新元素,并删除了一个元素。最后,我们使用contains()方法和indexOf()方法查找元素,并输出结果。可以看到,ArrayList的操作均正确无误。

测试代码分析

  根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。

这是一个用于演示 ArrayList 使用的 Java 类,主要实现了以下功能:

  1. 创建一个空的 ArrayList 对象;
  2. 向 ArrayList 中添加元素;
  3. 遍历 ArrayList 中的元素;
  4. 插入元素到指定位置;
  5. 删除元素;
  6. 查找元素在 ArrayList 中的位置。

  在 main 方法中,创建了一个空的 ArrayList 对象 list,并向其中添加了三个元素:Java、Python 和 C++。接着使用 for 循环遍历了 list 中的元素,并对每个元素进行了输出。然后调用了 list.add(1, "JavaScript") 方法,在 list 中第二个位置插入了一个元素 JavaScript。接着又调用了 list.remove("C++") 方法,将 list 中的 C++ 元素删除。最后通过 list.contains("Java") 方法查找元素 Java 是否在 list 中,并使用 list.indexOf("Python") 方法查找元素 Python 在 list 中的位置,将查找结果输出。

  总体来说,这个类演示了 ArrayList 的基本使用方法,包括添加、删除、插入和查找元素等。

总结

  本文详细介绍了Java中的ArrayList类,包括其定义和特点、常用操作技巧和方法、源代码解析、应用场景案例分析以及优缺点分析等方面。文章通过测试用例对ArrayList的操作进行了验证,并给读者提供了一些学习建议和推荐。通过本文的学习,读者可以更好地掌握ArrayList的使用方法和技巧,快速地进行开发。

  ...
  好啦,这期的内容就基本接近尾声啦,若你想学习更多,可以参考这篇专栏总结《「滚雪球学Java」教程导航帖》,本专栏致力打造最硬核 Java 零基础系列学习内容,🚀打造全网精品硬核专栏,带你直线超车;欢迎大家订阅持续学习。

附录源码

  如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。

☀️建议/推荐你


  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。

  最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。


目录
相关文章
|
1天前
|
存储 Java API
Java数据结构之ArrayList(如果想知道Java中有关ArrayList的知识点,那么只看这一篇就足够了!)
Java数据结构之ArrayList(如果想知道Java中有关ArrayList的知识点,那么只看这一篇就足够了!)
Java数据结构之ArrayList(如果想知道Java中有关ArrayList的知识点,那么只看这一篇就足够了!)
|
1天前
|
存储 Java 索引
Java中ArrayList(顺序表)的自我实现(如果想知道Java中怎么自我实现ArrayList,那么只看这一篇就足够了!)
Java中ArrayList(顺序表)的自我实现(如果想知道Java中怎么自我实现ArrayList,那么只看这一篇就足够了!)
|
3天前
|
NoSQL Java Redis
如何在 Java 中操作这些 Redis 数据结构的基本方法
如何在 Java 中操作这些 Redis 数据结构的基本方法
9 2
|
4天前
|
安全 Java 程序员
【程序猿逆袭指南】Java高手的秘密武器:throws关键字,让你的方法签名霸气侧漏!
【6月更文挑战第19天】`throws`关键字是Java异常处理的关键,用于方法签名中声明可能抛出的异常,提示调用者处理。它增进代码可读性和安全性,避免运行时崩溃。通过`throws`声明多个异常,能精细规划错误处理。掌握其使用,能提升代码质量和程序员的专业形象,是Java编程中的必备技能。
|
4天前
|
安全 Java 开发者
【技术咖必看】Java异常处理新境界:throws关键字,打造万无一失的方法签名!
【6月更文挑战第19天】在Java异常处理中,`throws`关键字用于方法签名,声明可能抛出的异常,提示调用者必须处理。它区分运行时异常和检查型异常,常用于声明需要调用者捕获的检查型异常。例如,`readFile`方法`throws IOException`,强制调用者通过try-catch或同样`throws`。多异常声明允许一次声明多个可能的异常类型,增强代码健壮性。理解并善用`throws`能构建更稳定、可读的代码。
|
5天前
|
存储 Java 索引
【Java】LinkedList vs. ArrayList:Java中的数据结构选择
【Java】LinkedList vs. ArrayList:Java中的数据结构选择
12 3
|
5天前
|
Java
在 Java 中,类是一种定义对象的模板,它包含数据成员(字段)和方法。
在 Java 中,类是一种定义对象的模板,它包含数据成员(字段)和方法。
|
1天前
|
Java API 索引
java中ArrayList类常用API
java中ArrayList类常用API
|
2天前
|
Java 程序员
java截取字符串的几种方法
java截取字符串的几种方法
|
2天前
|
安全 Java
JAVA反射调用方法
JAVA反射调用方法