Java 中 List 与 ArrayList 的比较
Java 中 List 与 ArrayList 的比较
1. 概述
在本文中,我们将探讨使用 List 和 ArrayList 类型的区别。
首先,我们将看到一个使用 ArrayList 的示例实现。然后,我们将切换到 List 接口并比较它们的差异。
2. 使用 ArrayList
ArrayList 是 Java 中最常用的 List 实现之一。它建立在数组之上,可以随着我们添加/删除元素而动态增长和缩小。当我们知道列表会变得很大时,最好初始化一个具有初始容量的列表:
ArrayList``<String>`` list = new ArrayList<>(25);
通过使用 ArrayList 作为引用类型,我们可以使用 ArrayList API 中的方法,而这些方法不在 List API 中 —— 例如,ensureCapacity, trimToSize, 或 removeRange。
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()));
}
// 其他一些函数用于删除乘客,按目的地获取...
}
在这里,我们使用 ArrayList 类型来存储和返回乘客列表。由于最大乘客数为20,因此列表的初始容量设置为20。
2.2. 可变大小数据的问题
只要我们不需要更改我们正在使用的 List 类型,上述实现就可以正常工作。在我们的示例中,我们选择了 ArrayList,并认为它满足了我们的需求。
然而,假设随着应用程序的成熟,很明显乘客数量变化很大。例如,如果只有五个预订的乘客,而初始容量为20,则内存浪费为75%。假设我们决定切换到更高效的 List。
2.3. 更改实现类型
Java 提供了另一种名为 LinkedList 的 List 实现,用于存储可变大小的数据。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 类型。 ArrayList 和 LinkedList 类都是 List 接口的实现。因此,我们现在可以安全地更改基础实现,而不会对应用程序的其他部分造成任何干扰。类仍然像以前一样编译和工作。
4. 比较方法
如果我们在程序中使用具体的列表类型,则所有代码都不必要地与该列表类型耦合。这使得将来更改列表类型更加困难。
此外,Java 中的实用程序类返回抽象类型而不是具体类型。例如,以下实用程序函数返回 List 类型:
Collections.singletonList(...), Collections.unmodifiableList(...)
Arrays.asList(...), ArrayList.sublist(...)
特别是 ArrayList.sublist 返回 List 类型,即使原始对象是 ArrayList 类型。因此,List API 中的方法不保证返回相同类型的列表。
5. 结论
在本文中,我们检查了使用 List 与 ArrayList 类型的区别和最佳实践。
我们看到了引用具体类型可以使应用程序在稍后更改时变得脆弱。具体来说,当底层实现发生变化时,它会影响应用程序的其他层。因此,通常更倾向于使用最抽象的类型(顶级类/接口)而不是使用具体的引用类型。
如常,示例的源代码可在 GitHub 上获取。--- date: 2022-04-01 category:
- Java tag:
- List
- ArrayList head:
- meta
- name: keywords content: Java List vs ArrayList
Java 中 List 与 ArrayList 的比较
1. 概述
在本文中,我们将探讨使用 List 和 ArrayList 类型的区别。
首先,我们将看到一个使用 ArrayList 的示例实现。然后,我们将切换到 List 接口并比较它们的差异。
2. 使用 ArrayList
ArrayList 是 Java 中最常用的 List 实现之一。它建立在数组之上,可以随着我们添加/删除元素而动态增长和缩小。当我们知道列表会变得很大时,最好初始化一个具有初始容量的列表:
ArrayList``<String>`` list = new ArrayList<>(25);
通过使用 ArrayList 作为引用类型,我们可以使用 ArrayList API 中的方法,而这些方法不在 List API 中 —— 例如,ensureCapacity, trimToSize, 或 removeRange。
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<>(passengers.stream()
.filter(it -> it.getSource().equals(source))
.collect(Collectors.toList()));
}
// 其他一些函数用于删除乘客,按目的地获取...
}
在这里,我们使用 ArrayList 类型来存储和返回乘客列表。由于最大乘客数为20,因此列表的初始容量设置为20。
2.2. 可变大小数据的问题
只要我们不需要更改我们正在使用的 List 类型,上述实现就可以正常工作。在我们的示例中,我们选择了 ArrayList,并认为它满足了我们的需求。
然而,假设随着应用程序的成熟,很明显乘客数量变化很大。例如,如果只有五个预订的乘客,而初始容量为20,则内存浪费为75%。假设我们决定切换到更高效的 List。
2.3. 更改实现类型
Java 提供了另一种名为 LinkedList 的 List 实现,用于存储可变大小的数据。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 类型。 ArrayList 和 LinkedList 类都是 List 接口的实现。因此,我们现在可以安全地更改基础实现,而不会对应用程序的其他部分造成任何干扰。类仍然像以前一样编译和工作。
4. 比较方法
如果我们在程序中使用具体的列表类型,则所有代码都不必要地与该列表类型耦合。这使得将来更改列表类型更加困难。
此外,Java 中的实用程序类返回抽象类型而不是具体类型。例如,以下实用程序函数返回 List 类型:
Collections.singletonList(...), Collections.unmodifiableList(...)
Arrays.asList(...), ArrayList.sublist(...)
特别是 ArrayList.sublist 返回 List 类型,即使原始对象是 ArrayList 类型。因此,List API 中的方法不保证返回一个相同类型的列表。
5. 结论
在本文中,我们检查了使用 List 与 ArrayList 类型的区别和最佳实践。
我们看到了引用具体类型可以使应用程序在稍后更改时变得脆弱。具体来说,当底层实现发生变化时,它会影响应用程序的其他层。因此,通常更倾向于使用最抽象的类型(顶级类/接口)而不是使用具体的引用类型。
如常,示例的源代码可在 GitHub 上获取。
OK