杰瑞科技汇

Java数组与ArrayList有何区别?

  1. 核心概念与定义
  2. 主要区别对比表
  3. 详细特性分析
  4. 代码示例
  5. 如何选择:何时用数组,何时用 ArrayList

核心概念与定义

数组

  • 定义:数组是一个固定长度的、用来存储相同类型数据的数据结构。
  • 特点
    • 长度固定:一旦创建,其大小就不能改变。
    • 类型统一:只能存储一种数据类型(int[] 只能存 intString[] 只能存 String)。
    • 底层连续:在内存中是连续存储的,这使得通过索引访问元素非常快(时间复杂度为 O(1))。
    • 性能高:由于内存连续,访问速度极快,也基本没有额外的方法调用开销。

ArrayList

  • 定义ArrayList 是 Java 集合框架 (java.util.ArrayList) 中的一个类,它实现了 List 接口,它本质上是一个动态数组
  • 特点
    • 长度可变:可以根据需要动态地增加或减少元素的大小。
    • 类型安全(泛型):通过泛型(ArrayList<String>)可以确保只能存储指定类型的数据,避免了类型转换的错误。
    • 底层是数组ArrayList 的内部实现就是一个数组,当元素数量超过当前数组容量时,它会创建一个更大的新数组(通常是原容量的 1.5 倍),然后将旧数组的元素复制到新数组中,这个过程被称为“扩容”(Resizing)。
    • 提供丰富方法:封装了大量便捷的方法,如 add(), remove(), size(), get() 等,使用起来非常方便。

主要区别对比表

特性 数组 ArrayList
长度 固定,创建后大小不可变。 可变,可以动态添加或删除元素。
类型 可以存储基本类型(int, char 等)和对象。 只能存储对象,如果要存基本类型,必须使用其包装类(如 Integer, Character)。
功能/方法 功能非常有限,只有 length 属性和一些 java.lang.reflect 方法。 功能非常丰富,提供了 add(), remove(), get(), size(), contains() 等大量方法。
性能 访问(读/写)速度快,直接通过索引计算内存地址。 访问速度也很快,但比原生数组稍慢,因为有方法调用开销。添加/删除元素在中间位置较慢,因为可能需要移动大量元素。
声明与初始化 int[] arr = new int[10];
String[] names = {"A", "B"};
ArrayList<String> list = new ArrayList<>();
list.add("A"); list.add("B");
可以直接存基本类型和对象。 只能存对象,存基本类型时用自动装箱。
多维 支持多维数组,如 int[][] matrix 不直接支持“真正的”多维数组,但可以通过 ArrayList<ArrayList<Integer>> 来模拟。
父类/接口 是 Java 的第一类对象,有 Object 作为隐式父类。 实现 List 接口,继承自 AbstractList

详细特性分析

数组的详细分析

优点:

Java数组与ArrayList有何区别?-图1
(图片来源网络,侵删)
  • 性能极致:对于已知大小的数据集,数组是最高效的选择,内存访问是直接的,没有额外的对象和方法调用。
  • 内存紧凑:在内存中是连续的,没有额外的指针或对象头开销。

缺点:

  • 长度固定:这是最大的缺点,如果不知道要存多少数据,数组的大小很难确定,定义太大浪费内存,定义太小又会导致程序出错(ArrayIndexOutOfBoundsException)。
  • 功能匮乏:没有内置的方法来排序、搜索、添加或删除元素,所有这些操作都需要自己手动实现,非常繁琐。

ArrayList 的详细分析

优点:

  • 动态扩容:解决了数组长度固定的问题,使用起来非常灵活。
  • 功能强大:提供了丰富的 API,极大简化了开发。list.remove(0) 可以轻松移除第一个元素,而在数组中这需要手动移动所有后续元素。
  • 代码可读性高list.add("item")arr[index++] = "item" 更直观。

缺点:

  • 性能开销
    • 扩容成本:当 ArrayList 频繁添加元素导致多次扩容时,复制数组会带来一定的性能开销。
    • 中间插入/删除慢:由于底层数组的连续性,在中间位置插入或删除一个元素,平均需要移动一半的元素,时间复杂度为 O(n)。
  • 空间开销:为了支持动态扩容,ArrayList 内部通常会预留一部分容量(capacity),这可能导致比数组占用更多的内存。
  • 不能存基本类型:虽然可以通过自动装箱(int -> Integer)解决,但会带来额外的对象创建和内存开销,并且可能产生 NullPointerException

代码示例

数组示例

public class ArrayExample {
    public static void main(String[] args) {
        // 1. 声明并初始化一个固定长度的整型数组
        int[] numbers = new int[5]; // 长度为 5
        // 2. 赋值
        numbers[0] = 10;
        numbers[1] = 20;
        numbers[2] = 30;
        numbers[3] = 40;
        numbers[4] = 50;
        // 3. 访问元素
        System.out.println("第一个元素是: " + numbers[0]); // 输出: 第一个元素是: 10
        // 4. 获取数组长度
        System.out.println("数组长度是: " + numbers.length); // 输出: 数组长度是: 5
        // 5. 遍历数组
        System.out.println("遍历数组:");
        for (int i = 0; i < numbers.length; i++) {
            System.out.print(numbers[i] + " "); // 输出: 10 20 30 40 50
        }
        System.out.println();
        // 6. 数组的缺点:长度固定,尝试添加第6个元素会抛出异常
        // numbers[5] = 60; // 编译不通过,数组索引越界
    }
}

ArrayList 示例

import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
    public static void main(String[] args) {
        // 1. 声明并初始化一个 ArrayList (只能存储 String 对象)
        List<String> names = new ArrayList<>(); // 使用接口 List 声明是更好的编程习惯
        // 2. 添加元素 (长度可变)
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        System.out.println("初始列表: " + names); // 输出: [Alice, Bob, Charlie]
        // 3. 在指定位置添加元素
        names.add(1, "David");
        System.out.println("在索引1处添加David后: " + names); // 输出: [Alice, David, Bob, Charlie]
        // 4. 获取元素
        System.out.println("索引为1的元素是: " + names.get(1)); // 输出: David
        // 5. 获取列表大小 (注意不是 length)
        System.out.println("列表大小是: " + names.size()); // 输出: 4
        // 6. 移除元素
        names.remove("Alice"); // 移除第一个匹配的元素
        System.out.println("移除Alice后: " + names); // 输出: [David, Bob, Charlie]
        names.remove(0); // 移除指定索引的元素
        System.out.println("移除索引0后: " + names); // 输出: [Bob, Charlie]
        // 7. 检查元素是否存在
        System.out.println("列表中是否包含 'Bob'? " + names.contains("Bob")); // 输出: true
        // 8. 遍历 ArrayList (多种方式)
        System.out.println("使用 for-each 遍历:");
        for (String name : names) {
            System.out.print(name + " "); // 输出: Bob Charlie
        }
        System.out.println();
    }
}

如何选择:何时用数组,何时用 ArrayList

这是一个非常常见且重要的问题,遵循以下原则可以做出正确的选择:

Java数组与ArrayList有何区别?-图2
(图片来源网络,侵删)

优先选择 ArrayList 的情况:

  1. 不确定数据量大小:如果你不知道最终需要存储多少个元素,或者元素数量会动态变化,ArrayList 是不二之选。
  2. 需要频繁的增删操作:特别是对列表头或中间进行增删,ArrayList 提供了简洁的 API,而数组操作非常复杂。
  3. 需要丰富的集合操作:如果你需要排序、查找、清空列表、检查元素是否存在等,直接使用 ArrayList 的方法即可,无需自己实现。
  4. 代码简洁性和可读性优先ArrayList 的方法调用(如 add, remove)比手动操作数组索引更直观、更不易出错。

在 90% 的日常开发场景中,都应该优先使用 ArrayList

选择数组的情况:

  1. 性能是绝对优先级:在处理大规模数据(如游戏引擎、科学计算、高频交易)时,对内存访问速度要求极致,数组的直接内存访问是 ArrayList 无法比拟的。
  2. 数据量固定且已知:如果你明确知道数据量不会改变(一年中的12个月,一周中的7天),使用数组更高效,因为它没有 ArrayList 的扩容和对象开销。
  3. 作为 ArrayList 或其他集合的底层实现ArrayList 本身就是用数组实现的,当你需要自己实现高性能数据结构时,数组是基础。
  4. 与 JNI 或其他本地代码交互:在调用本地 C/C++ 代码时,通常需要传递原始的数组指针,Java 数组可以方便地做到这一点。

只有在性能极其敏感、数据量固定且明确知道大小的场景下,才考虑使用原生数组。

希望这个详细的对比能帮助你彻底理解 Java 数组和 ArrayList 的区别与用法!

Java数组与ArrayList有何区别?-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇