List vs. ArrayList in Java – Java中的List与ArrayList

最后修改: 2022年 4月 15日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’ll look into the differences between using the List and ArrayList types.

在这篇文章中,我们将研究使用ListArrayList类型之间的区别。

First, we’ll see a sample implementation using ArrayList. Then, we’ll switch to the List interface and compare the differences.

首先,我们将看到一个使用ArrayList的示例实现。然后,我们将切换到List接口,并比较两者的区别。

2. Using ArrayList

2.使用ArrayList

ArrayList is one of the most commonly used List implementations in Java. It’s built on top of an array, which can dynamically grow and shrink as we add/remove elements. It’s good to initialize a list with an initial capacity when we know that it will get large:

ArrayList是Java中最常用的List实现之一。它建立在一个数组之上,当我们添加/删除元素时,数组可以动态地增长和缩小。当我们知道列表会变大时,用一个初始容量来初始化它是很好的。

ArrayList<String> list = new ArrayList<>(25);

By using ArrayList as a reference type, we can use methods in the ArrayList API that are not in the List API — for example, ensureCapacity, trimToSize, or removeRange.

通过使用ArrayList作为引用类型,我们可以使用ArrayList API中不在List API中的方法–例如,ensureCapacity、trimToSizeremoveRange

2.1. Quick Example

2.1.快速实例

Let’s write a basic passenger processing application:

让我们来写一个基本的旅客处理应用程序。

public class ArrayListDemo {
    private ArrayList<Passenger> passengers = new ArrayList<>(20);

    public ArrayList<Passenger> addPassenger(Passenger passenger) {
        passengers.add(passenger);
        return passengers;
    }
    
    public ArrayList<Passenger> getPassengersBySource(String source) {
        return new ArrayList<Passenger>(passengers.stream()
            .filter(it -> it.getSource().equals(source))
            .collect(Collectors.toList()));
    }
    
    // Few other functions to remove passenger, get by destination, ... 
}

Here, we’ve used the ArrayList type to store and return the list of passengers. Since the maximum number of passengers is 20, the initial capacity for the list is set to this.

在这里,我们使用了ArrayList类型来存储和返回乘客的列表。由于乘客的最大数量是20,所以列表的初始容量被设置为这个。

2.2. The Problem with Variable Sized Data

2.2.可变大小数据的问题

The above implementation works fine so long as we don’t need to change the type of List we’re using. In our example, we chose ArrayList and felt it met our needs.

只要我们不需要改变我们所使用的List的类型,上面的实现就可以正常工作。在我们的例子中,我们选择了ArrayList,觉得它能满足我们的需求。

However, let’s assume that as the application matures, it becomes clear that the number of passengers varies quite a lot. For instance, if there are only five booked passengers, with an initial capacity of 20, the memory wastage is 75%. Let’s say we decide to switch to a more memory-efficient List.

然而,让我们假设随着应用的成熟,乘客的数量显然有相当大的变化。例如,如果只有5名预订的乘客,初始容量为20,那么内存浪费为75%。假设我们决定切换到一个更节省内存的List

2.3. Changing the Implementation Type

2.3.改变实现类型

Java provides another List implementation called LinkedList to store variable-sized data. LinkedList uses a collection of linked nodes to store and retrieve elements. What if we decided to change the base implementation from ArrayList to LinkedList:

Java 提供了另一种 List 实现,称为 LinkedList,用于存储可变大小的数据 LinkedList 使用一个链接节点的集合来存储和检索元素。如果我们决定将基础实现从ArrayList改为LinkedList

private LinkedList<Passenger> passengers = new LinkedList<>();

This change affects more parts of the application because all the functions in the demo application expect to work with the ArrayList type.

这一变化影响了应用程序的更多部分,因为演示应用程序中的所有函数都期望与ArrayList类型一起工作

3. Switching to List

3.切换到清单

Let’s see how can we handle this situation by using the List interface type:

让我们看看如何通过使用List接口类型来处理这种情况。

private List<Passenger> passengers = new ArrayList<>(20);

Here, we’re using the List interface as the reference type instead of the more specific ArrayList type. We can apply the same principle to all the function calls and return types. For example:

这里,我们使用List接口作为引用类型,而不是更具体的ArrayList类型。我们可以将同样的原则应用于所有的函数调用和返回类型。比如说。

public List<Passenger> getPassengersBySource(String source) {
    return passengers.stream()
        .filter(it -> it.getSource().equals(source))
        .collect(Collectors.toList());
}

Now, let’s consider the same problem statement and change the base implementation to the LinkedList type. Both the ArrayList and LinkedList classes are implementations of the List interface. So, we can now safely change the base implementation without creating any disturbances to other parts of the application. The class still compiles and works fine as before.

现在,让我们考虑同样的问题陈述,并将基础实现改为LinkedList类型。ArrayListLinkedList类都是对List接口的实现。因此,我们现在可以安全地改变基础实现而不对应用程序的其他部分造成任何干扰。该类仍然可以像以前一样编译并正常工作。

4. Comparing the Approaches

4.比较各种方法

If we use a concrete list type throughout the program, then all of our code is coupled with that list type unnecessarily. This makes it harder to change list types in the future.

如果我们在整个程序中使用一个具体的列表类型,那么我们所有的代码都会不必要地与这个列表类型相耦合。这使得将来要改变列表类型变得更加困难。

In addition, the utility classes available in Java return the abstract type rather than the concrete type. For instance, utility functions below return the List type:

此外,在Java中可用的实用类返回抽象类型而不是具体类型。例如,下面的实用函数返回List类型。

Collections.singletonList(...), Collections.unmodifiableList(...)
Arrays.asList(...), ArrayList.sublist(...)

Specifically, ArrayList.sublist returns the List type, even though the original object is of ArrayList type. As such, methods in the List API don’t guarantee to return a list of the same type.

具体来说,ArrayList.sublist返回List类型,即使原始对象是ArrayList类型。因此,List API 中的方法并不能保证返回相同类型的列表。

5. Conclusion

5.总结

In this article, we examined the differences and best practices of using List vs ArrayList types.

在这篇文章中,我们研究了使用ListArrayList类型的区别和最佳实践。

We saw how referencing a specific type can make the application vulnerable to change at a later point in time. Specifically, when the underlying implementation changes, it affects other layers of the application. Hence, using the most abstract type (top-level class/interface) is often preferred over using a specific reference type.

我们看到,引用一个特定的类型会使应用程序在以后的时间点上容易发生变化。具体来说,当底层实现发生变化时,会影响到应用程序的其他层。因此,使用最抽象的类型(顶层类/接口)通常比使用特定的引用类型要好。

As always, the source code for the examples is available over on GitHub.

像往常一样,这些例子的源代码可以在GitHub上找到