杰瑞科技汇

ArrayList与List有何区别?

核心关系:接口与实现

List 是一个接口,而 ArrayList 是这个接口的一个具体实现类。

ArrayList与List有何区别?-图1
(图片来源网络,侵删)

你可以把它们的关系想象成:

  • List 是一份“菜谱”(接口),它规定了“一列有序的、可以重复的元素”应该具备哪些功能(比如添加、删除、获取、获取大小等)。
  • ArrayList 是一位“厨师”(实现类),他根据这份“菜谱”(List 接口)的规则,用特定的“食材和烹饪方法”(底层数据结构 Object[] 数组)做出了一个具体的“菜品”(ArrayList 对象)。

详细对比

特性 List (接口) ArrayList (类)
本质 一个接口,定义了一组规范(契约)。 一个具体的类,实现了 List 接口,并提供了具体的实现。
实例化 不能被直接实例化,它是一个抽象概念。 可以被直接实例化,创建一个对象。
语法 List<String> myList = new ArrayList<>(); (推荐) ArrayList<String> myList = new ArrayList<>();
功能 只包含方法的声明(如 add(), get(), size()),没有具体实现。 提供了 List 接口中所有方法的具体实现,并可能添加一些自己的方法(如 ensureCapacity())。
底层结构 不定义,任何实现 List 接口的类都可以使用自己的底层结构。 使用动态数组Object[])作为底层存储。
性能特点 不确定,取决于你用的是哪个实现类(ArrayList, LinkedList 等)。 - 随机访问(查询)快get(index) 时间复杂度为 O(1),因为数组通过下标访问元素非常快。
- 尾部添加/删除较快:在不考虑扩容的情况下,add(E)remove(size()-1) 的时间复杂度为 O(1)。
- 中间插入/删除慢add(index, E)remove(index) 时间复杂度为 O(n),因为需要移动后续所有元素。
灵活性 非常灵活,在代码中,你只依赖 List 接口,而不关心具体实现,这样未来可以轻松切换为 LinkedList 或其他 List 实现,而无需修改大量代码。 不够灵活,代码直接依赖于 ArrayList 类,如果未来想换成 LinkedList,就需要修改所有声明的地方。

代码示例

如何声明和创建(最佳实践)

这是最推荐的方式:面向接口编程

import java.util.ArrayList;
import java.util.List;
public class ListExample {
    public static void main(String[] args) {
        // 1. 声明为 List 接口,但实例化为 ArrayList 类
        // 这体现了“面向接口编程”的思想,代码耦合度低,易于扩展。
        List<String> names = new ArrayList<>();
        // 也可以这样写,但上面的方式更通用
        // ArrayList<String> names = new ArrayList<>();
        // 2. 添加元素
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        // 3. 获取元素
        String firstPerson = names.get(0); // "Alice"
        System.out.println("First person: " + firstPerson);
        // 4. 获取大小
        System.out.println("Number of people: " + names.size()); // 3
        // 5. 遍历
        System.out.println("All names:");
        for (String name : names) {
            System.out.println(name);
        }
    }
}

为什么要用 List 而不是 ArrayList 声明?(多态和可替换性)

假设你的业务逻辑需要处理一个“名字列表”。

不好的做法(紧耦合):

ArrayList与List有何区别?-图2
(图片来源网络,侵删)
// 如果你的方法只接受 ArrayList,那么未来如果数据源是 LinkedList,就无法使用
public void processNames(ArrayList<String> names) {
    // ...
}

好的做法(松耦合):

// 这个方法只关心它是一个 List,不关心它具体是 ArrayList 还是 LinkedList
public void processNames(List<String> names) {
    // 这个方法可以处理任何实现了 List 接口的对象
    System.out.println("Processing " + names.size() + " names.");
}
public static void main(String[] args) {
    // 可以轻松地传入 ArrayList
    List<String> fromArrayList = new ArrayList<>();
    fromArrayList.add("Alice");
    processNames(fromArrayList);
    // 也可以轻松地传入 LinkedList,而无需修改 processNames 方法
    List<String> fromLinkedList = new java.util.LinkedList<>();
    fromLinkedList.add("Bob");
    processNames(fromLinkedList);
}

这种设计使得你的代码更加健壮和易于维护。


何时使用哪个?

什么时候应该使用 List

方法参数、返回类型和成员变量的声明中,永远优先使用 List 接口

  • 方法参数public void setData(List<Data> dataList)
  • 返回类型public List<Item> getItems()
  • 类成员变量private List<User> users;

这样做的好处是:

ArrayList与List有何区别?-图3
(图片来源网络,侵删)
  1. 灵活性:可以随时更换 List 的具体实现(从 ArrayList 换成 LinkedList),而调用方代码无需任何改动。
  2. 隐藏实现细节:你只向外部暴露了“这是一个列表”这一信息,而没有暴露“它是一个基于数组实现的列表”这个内部实现。

什么时候应该使用 ArrayList

当你需要创建一个 List 对象,或者需要使用 ArrayList 特有的方法时。

  • 创建实例List<String> list = new ArrayList<>();
  • 使用特有方法ArrayList 有一些 List 接口没有的方法,
    • void ensureCapacity(int minCapacity): 手动增加底层数组的容量,以减少扩容次数。
    • void trimToSize(): 将底层数组的大小调整为列表当前的大小,释放多余空间。

在绝大多数情况下,你只需要 List 接口定义的方法,很少会用到 ArrayList 的特有方法。


List ArrayList
角色 蓝图/契约 具体的房子
用途 定义行为规范,用于声明,实现解耦 提供具体实现,用于创建对象,处理具体数据
核心思想 面向接口编程 面向具体实现

记住这个黄金法则:

在声明时使用接口(List),在创建时使用实现类(ArrayList)。

这是 Java 编程中一项非常重要的最佳实践,能极大地提升代码的可读性、可维护性和灵活性。

分享:
扫描分享到社交APP
上一篇
下一篇