在您的Java应用程序中,通常会使用各种类型的对象。您可能希望对这些对象执行排序、搜索和迭代等操作。
在JDK 1.2之前引入Collections框架之前,您可能会使用Arrays和Vectors来存储和管理一组对象。但它们也有自己的缺点。
Java集合框架旨在通过提供常见数据结构的高性能实现来克服这些问题。这使您可以专注于编写应用程序逻辑,而不是专注于低级操作。
然后,在JDK 1.5中引入泛型显著改进了Java集合框架。泛型允许您强制执行存储在集合中的对象的类型安全性,从而增强了应用程序的健壮性。您可以在这里阅读更多关于Java泛型的信息。here。
在本文中,我将指导您如何使用Java集合框架。我们将讨论不同类型的集合,如列表、集合、队列和映射。我还将简要解释它们的关键特性,例如:
-
内部机制
-
处理重复项
-
支持空值
-
排序
-
同步
-
性能
-
关键方法
-
常见实现
我们还将通过一些代码示例进行详细说明,并介绍Collections实用类及其用法。
目录:
理解Java集合框架
根据Java 文档,“集合是表示一组对象的对象。集合框架是用于表示和操作集合的统一架构。”
简单来说,Java集合框架帮助您管理一组对象,并以高效和有组织的方式对其执行操作。它通过提供各种方法来处理对象组,使应用程序开发变得更加容易。使用Java集合框架可以有效地添加、删除、搜索和排序对象。
集合接口
在Java中,接口指定了任何实现它的类必须遵守的合同。这意味着实现类必须为接口中声明的所有方法提供具体实现。
在Java集合框架中,诸如Set
、List
和Queue
等各种集合接口都扩展了Collection
接口,并且它们必须遵守Collection
接口定义的契约。
解读Java集合框架层次结构
查看这篇文章中展示Java集合层次结构的精美图表:
我们将从顶部开始逐步向下介绍,以便您了解这个图表展示的内容:
-
在Java集合框架的根部是
Iterable
接口,它允许您对集合的元素进行迭代。 -
Collection
接口扩展了Iterable
接口。这意味着它继承了Iterable
接口的属性和行为,并为添加、移除和检索元素添加了自己的行为。 -
诸如
List
、Set
和Queue
之类的特定接口进一步扩展了Collection
接口。这些接口中的每一个都有其他类来实现它们的方法。例如,ArrayList
是List
接口的常用实现,HashSet
实现了Set
接口,诸如此类。 -
Map
接口是Java集合框架的一部分,但与上述其他接口不同,它并未扩展Collection
接口。 -
该框架中的所有接口和类都属于
java.util
包。
注意:Java集合框架中常见的混淆点在于Collection
与Collections
之间的区别。Collection
是框架中的一个接口,而Collections
是一个实用类。Collections
类提供了对集合元素执行操作的静态方法。
Java集合接口
到目前为止,您应该已经熟悉构成集合框架基础的不同类型集合。现在我们将更仔细地看一下List
、Set
、Queue
和Map
接口。
在本节中,我们将讨论每个接口,同时探索它们的内部机制。我们将研究它们如何处理重复元素,以及它们是否支持插入空值。我们还将了解插入期间元素的排序以及它们对同步的支持,涉及线程安全的概念。然后我们将逐个讨论这些接口的一些关键方法,并通过审查常见实现及其对各种操作的性能来结束。
在开始之前,让我们简要讨论一下同步和性能。
-
同步通过多个线程控制对共享对象的访问,确保它们的完整性并防止冲突。这对于保持线程安全至关重要。
-
在选择集合类型时,一个重要因素是其在常见操作(如插入、删除和检索)期间的性能。性能通常使用大O符号表示。您可以在这里了解更多信息。
列表
列表是一种有序或顺序集合。它遵循从零开始的索引,允许通过它们的索引位置插入、删除或访问元素。
-
内部机制:列表在内部由数组或链表支持,具体取决于实现类型。例如,ArrayList使用数组,而LinkedList在内部使用链表。您可以在这里阅读更多关于LinkedList的信息。列表在添加或删除元素时动态调整大小。基于索引的检索使其成为一种非常高效的集合类型。
-
重复项:
List
中允许重复元素,这意味着List
中可以有多个具有相同值的元素。可以根据存储的索引检索任何值。 -
空值:
List
中也允许空值。由于允许重复,您也可以拥有多个空元素。 -
顺序:一个
List
维护插入顺序,意味着元素以添加的顺序存储。当您希望以插入顺序检索元素时,这将非常有用。 -
同步: 一个
列表
默认情况下不是同步的,这意味着它没有内置的处理同时被多个线程访问的方式。 -
关键方法:这是一个
List
接口的一些关键方法:add(E element)
、get(int index)
、set(int index, E element)
、remove(int index)
和size()
。让我们通过一个示例程序来看看如何使用这些方法。import java.util.ArrayList; import java.util.List; public class ListExample { public static void main(String[] args) { // 创建一个列表 List<String> list = new ArrayList<>(); // add(E element) list.add("Apple"); list.add("Banana"); list.add("Cherry"); // get(int index) String secondElement = list.get(1); // "Banana" // set(int index, E element) list.set(1, "Blueberry"); // remove(int index) list.remove(0); // 移除 "Apple" // size() int size = list.size(); // 2 // 打印列表 System.out.println(list); // 输出: [Blueberry, Cherry] // 打印列表的大小 System.out.println(size); // 输出: 2 } }
-
常见实现:
ArrayList
,LinkedList
,Vector
,Stack
-
性能: 通常,在
ArrayList
和LinkedList
中,插入和删除操作都很快。但获取元素可能会很慢,因为你必须遍历节点。
操作 | ArrayList | LinkedList |
插入 | 在末尾快 – 摊销O(1),在开头或中间慢 – O(n) | 在开头或中间快 – O(1),在末尾慢 – O(n) |
删除 | 在末尾快 – 摊销O(1),在开头或中间慢 – O(n) | 快 – 如果位置已知,则为O(1) |
检索 | 快 – 对于随机访问为O(1) | 慢 – 对于随机访问为O(n),因为会涉及遍历 |
集合
Set
是一种不允许重复元素的集合类型,代表数学集合的概念。
-
内部机制: 一个
Set
在内部由一个HashMap
支持。根据实现类型的不同,它可以由HashMap
,LinkedHashMap
或TreeMap
支持。我已经写了一篇关于HashMap
内部工作原理的详细文章在这里。一定要去看一下。 -
重复项: 由于
Set
代表数学集合的概念,不允许重复元素。这确保了所有元素都是唯一的,保持了集合的完整性。 -
Null:在
Set
中最多允许一个空值,因为不允许重复。但这不适用于TreeSet
的实现,TreeSet
不允许空值。 -
Ordering:在
Set
中元素的顺序取决于实现的类型。-
HashSet
:顺序不保证,元素可以放在任何位置。 -
LinkedHashSet
:这个实现会保持插入顺序,因此您可以按照插入顺序检索元素。 -
TreeSet
:元素根据其自然顺序插入。或者,您可以通过指定自定义比较器来控制插入顺序。
-
-
同步:一个
Set
不是同步的,这意味着如果两个或多个线程同时尝试访问一个Set
对象,则可能会遇到并发问题,如竞态条件,这可能会影响数据完整性 -
关键方法: 这里是
Set
接口的一些关键方法:add(E element)
,remove(Object o)
,contains(Object o)
和size()
。让我们看看如何在一个示例程序中使用这些方法。import java.util.HashSet; import java.util.Set; public class SetExample { public static void main(String[] args) { // 创建一个集合 Set<String> set = new HashSet<>(); // 向集合中添加元素 set.add("Apple"); set.add("Banana"); set.add("Cherry"); // 从集合中移除一个元素 set.remove("Banana"); // 检查集合是否包含一个元素 boolean containsApple = set.contains("Apple"); System.out.println("包含 Apple: " + containsApple); // 获取集合的大小 int size = set.size(); System.out.println("集合大小: " + size); } }
-
常见实现:
HashSet
、LinkedHashSet
、TreeSet
-
性能:
Set
的实现在基本操作上具有快速性能,除了TreeSet
,在这种情况下,性能可能相对较慢,因为内部数据结构在这些操作期间涉及对元素进行排序。
操作 | HashSet | LinkedHashSet | TreeSet |
插入 | 快速 – O(1) | 快速 – O(1) | 较慢 – O(log n) |
删除 | 快速 – O(1) | 快速 – O(1) | 较慢 – O(log n) |
检索 | 快速 – O(1) | 快速 – O(1) | 较慢 – O(log n) |
队列
队列是一种线性集合,用于在处理之前保存多个项目,通常按照FIFO(先进先出)顺序进行。这意味着元素在一端添加,在另一端移除,因此添加到队列的第一个元素是第一个被移除的元素。
-
内部机制:队列的内部工作方式可以根据其特定实现而有所不同。
-
LinkedList
– 使用双向链表存储元素,这意味着可以向前和向后遍历,实现灵活的操作。 -
PriorityQueue
– 内部由二叉堆支持,非常高效用于检索操作。 -
ArrayDeque
– 使用数组实现,随着元素的添加或删除而扩展或缩小。在这里,元素可以从队列的两端添加或删除。
-
-
重复项: 在
Queue
中,允许重复元素,允许插入相同值的多个实例 -
空值: 不能向
Queue
插入空值,因为按设计,Queue
的某些方法返回null表示为空。为避免混淆,不允许空值。 -
排序:元素根据其自然顺序插入。或者,您可以通过指定自定义比较器来控制插入顺序。
-
同步:默认情况下,
Queue
不是同步的。但是,您可以使用ConcurrentLinkedQueue
或BlockingQueue
实现来实现线程安全。 -
关键方法:这是一个
Queue
接口的一些关键方法:add(E element)
、offer(E element)
、poll()
和peek()
。让我们看看如何使用这些方法来编写示例程序。import java.util.LinkedList; import java.util.Queue; public class QueueExample { public static void main(String[] args) { // 使用 LinkedList 创建一个队列 Queue<String> queue = new LinkedList<>(); // 使用 add 方法插入元素,如果插入失败会抛出异常 queue.add("Element1"); queue.add("Element2"); queue.add("Element3"); // 使用 offer 方法插入元素,如果插入失败会返回 false queue.offer("Element4"); // 显示队列 System.out.println("Queue: " + queue); // 查看第一个元素(不移除) String firstElement = queue.peek(); System.out.println("Peek: " + firstElement); // 输出 "Element1" // 获取并移除第一个元素 String polledElement = queue.poll(); System.out.println("Poll: " + polledElement); // 输出 "Element1" // 移除元素后显示队列 System.out.println("Queue after poll: " + queue); } }
-
常见实现:
LinkedList
,PriorityQueue
,ArrayDeque
-
性能: 像
LinkedList
和ArrayDeque
这样的实现通常对于添加和移除项目都很快。而PriorityQueue
会稍慢一些,因为它根据设定的优先级顺序插入项目。
操作 | LinkedList | PriorityQueue | ArrayDeque |
插入 | 开头或中间快 – O(1),末尾慢 – O(n) | 较慢 – O(log n) | 快 – O(1),慢 – O(n),如果涉及内部数组的调整 |
删除 | 快 – O(1)如果位置已知 | 较慢 – O(log n) | 快 – O(1),慢 – O(n),如果涉及内部数组的调整 |
检索 | 对于随机访问慢 – O(n),因为涉及遍历 | 快 – O(1) | 快 – O(1) |
映射
Map
表示键-值对的集合,每个键映射到单个值。尽管Map
是Java集合框架的一部分,但它不扩展java.util.Collection
接口。
-
内部机制:
Map
在内部使用基于哈希概念的HashTable
进行操作。我已经就这个主题撰写了一篇详细的 文章,请阅读以深入了解。 -
重复项:
Map
将数据存储为键值对。在这里,每个键都是唯一的,因此不允许重复键。但允许重复值。 -
空值:由于不允许重复键,一个
Map
只能有一个空键。由于允许重复值,可以有多个空值。在TreeMap
实现中,键不能为 null,因为它基于键对元素进行排序。但是,允许空值。 -
排序:
Map
的插入顺序取决于实现方式:-
HashMap
– 插入顺序不能保证,因为它们是根据哈希概念确定的。 -
LinkedHashMap
– 保留插入顺序,您可以按照添加到集合中的顺序检索元素。 -
TreeMap
– 元素根据其自然顺序插入。或者,您可以通过指定自定义比较器来控制插入顺序。
-
-
同步:默认情况下,
Map
不是同步的。但是您可以使用Collections.synchronizedMap()
或ConcurrentHashMap
实现来实现线程安全。 -
关键方法: 这里是
Map
接口的一些关键方法:put(K key, V value)
,get(Object key)
,remove(Object key)
,containsKey(Object key)
, 和keySet()
。让我们看看如何在示例程序中使用这些方法。import java.util.HashMap; import java.util.Map; import java.util.Set; public class MapMethodsExample { public static void main(String[] args) { // 创建一个新的HashMap Map<String, Integer> map = new HashMap<>(); // put(K key, V value) - 将键-值对插入到map中 map.put("Apple", 1); map.put("Banana", 2); map.put("Orange", 3); // get(Object key) - 返回与键关联的值 Integer value = map.get("Banana"); System.out.println("Value for 'Banana': " + value); // remove(Object key) - 移除指定键的键-值对 map.remove("Orange"); // containsKey(Object key) - 检查map是否包含指定键 boolean hasApple = map.containsKey("Apple"); System.out.println("Contains 'Apple': " + hasApple); // keySet() - 返回map中包含的键的集合视图 Set<String> keys = map.keySet(); System.out.println("Keys in map: " + keys); } }
-
常见实现:
HashMap
,LinkedHashMap
,TreeMap
,Hashtable
,ConcurrentHashMap
-
性能:
HashMap
实现被广泛使用,主要是因为其在下表中描述的高效性能特征。
操作 | HashMap | LinkedHashMap | TreeMap |
插入 | 快速 – O(1) | 快速 – O(1) | 较慢 – O(log n) |
删除 | 快速 – O(1) | 快速 – O(1) | 较慢 – O(log n) |
检索 | 快速 – O(1) | 快速 – O(1) | 较慢 – O(log n) |
集合实用类
正如本文开头所强调的,Collections
实用类具有几个有用的静态方法,可让您对集合元素执行常用操作。这些方法帮助您减少应用程序中的样板代码,并让您专注于业务逻辑。
以下是一些关键特性和方法,以及它们的简要介绍:
-
排序:
Collections.sort(List<T>)
– 该方法用于对列表中的元素按升序排序。 -
搜索:
Collections.binarySearch(List<T>, key)
– 该方法用于在排序列表中搜索特定元素并返回其索引。 -
倒序:
Collections.reverse(List<T>)
– 该方法用于颠倒列表中元素的顺序。 -
最小/最大操作:
Collections.min(Collection<T>)
和Collections.max(Collection<T>)
– 这些方法分别用于在集合中找到最小和最大的元素。 -
同步:
Collections.synchronizedList(List<T>)
– 该方法用于通过同步使列表线程安全。 -
不可修改的集合:
Collections.unmodifiableList(List<T>)
– 该方法用于创建列表的只读视图,防止修改。
以下是一个演示Collections
实用类各种功能的示例Java程序:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(3);
numbers.add(8);
numbers.add(1);
// 排序
Collections.sort(numbers);
System.out.println("Sorted List: " + numbers);
// 搜索
int index = Collections.binarySearch(numbers, 3);
System.out.println("Index of 3: " + index);
// 倒序
Collections.reverse(numbers);
System.out.println("Reversed List: " + numbers);
// 最小/最大操作
int min = Collections.min(numbers);
int max = Collections.max(numbers);
System.out.println("Min: " + min + ", Max: " + max);
// 同步
List<Integer> synchronizedList = Collections.synchronizedList(numbers);
System.out.println("Synchronized List: " + synchronizedList);
// 不可修改的集合
List<Integer> unmodifiableList = Collections.unmodifiableList(numbers);
System.out.println("Unmodifiable List: " + unmodifiableList);
}
}
该程序演示了排序、搜索、倒序、查找最小和最大值、同步以及使用Collections
实用类创建不可修改列表的功能。
结论
在本文中,您已了解Java集合框架及其如何帮助管理Java应用程序中的对象组。我们探讨了各种集合类型,如列表、集合、队列和映射,并深入了解了其中一些关键特征以及每种类型如何支持这些特征。
您已了解了性能、同步和关键方法,获得了选择适合您需求的正确数据结构的宝贵见解。
通过理解这些概念,您可以充分利用Java集合框架,从而编写更高效的代码并构建健壮的应用程序。
如果您觉得这篇文章有趣,请随时查看我在freeCodeCamp上的其他文章,并在LinkedIn上与我联系。
Source:
https://www.freecodecamp.org/news/java-collections-framework-reference-guide/