1. 概述

在这篇文章中,我们将探讨在 Java 中使用ListArrayList类型的区别。

首先,我们会展示一个使用ArrayList的示例实现。然后,我们将切换到List接口,并比较两者之间的差异。

2. 使用ArrayList

ArrayList是 Java 中最常用的List实现之一,它基于数组构建,可以根据添加或删除元素动态增长或缩小。当我们知道列表将变得很大时,最好初始化一个具有初始容量的列表:

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

作为ArrayList引用类型,我们可以使用ArrayList API 中不在List API 中的方法,如ensureCapacitytrimToSizeremoveRange

2.1. 快速示例

让我们编写一个基本的乘客处理应用程序:

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, ... 
}

在这里,我们使用ArrayList类型来存储和返回乘客列表。由于最多有20名乘客,因此列表的初始容量设置为此数。

2.2. 变量大小数据的问题

只要我们不需要更改使用的List类型,上述实现就很好。在我们的示例中,我们选择了ArrayList,并认为它满足了我们的需求。

然而,假设随着应用的成熟,我们发现乘客数量变化很大。例如,如果有5个预订乘客,而初始容量为20,那么内存浪费将达到75%。如果我们决定切换到更节省内存的List实现,问题就会出现。

2.3. 改变实现类型

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

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

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

3. 切换到List

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

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

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

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

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

4. 比较方法

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

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

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

特别是ArrayList.sublist返回List类型,尽管原始对象是ArrayList类型。因此,List API 的方法并不能保证返回相同类型的列表。

5. 总结

在这篇文章中,我们探讨了使用ListArrayList类型的不同之处以及最佳实践。

我们看到了如何引用特定类型会使应用程序在未来变得更容易受到变化的影响。特别是当底层实现发生变化时,它会影响到应用程序的其他层。因此,通常更倾向于使用最抽象的类型(顶级类/接口)而不是具体的引用类型。

如往常一样,示例代码可以在GitHub上找到。