在您的Java应用程序中,通常会使用各种类型的对象。您可能希望对这些对象执行排序、搜索和迭代等操作。

在JDK 1.2之前引入Collections框架之前,您可能会使用Arrays和Vectors来存储和管理一组对象。但它们也有自己的缺点。

Java集合框架旨在通过提供常见数据结构的高性能实现来克服这些问题。这使您可以专注于编写应用程序逻辑,而不是专注于低级操作。

然后,在JDK 1.5中引入泛型显著改进了Java集合框架。泛型允许您强制执行存储在集合中的对象的类型安全性,从而增强了应用程序的健壮性。您可以在这里阅读更多关于Java泛型的信息。here

在本文中,我将指导您如何使用Java集合框架。我们将讨论不同类型的集合,如列表、集合、队列和映射。我还将简要解释它们的关键特性,例如:

  • 内部机制

  • 处理重复项

  • 支持空值

  • 排序

  • 同步

  • 性能

  • 关键方法

  • 常见实现

我们还将通过一些代码示例进行详细说明,并介绍Collections实用类及其用法。

目录:

  1. 理解Java集合框架

  2. Java集合接口

  3. 集合实用类

  4. 结论

理解Java集合框架

根据Java 文档,“集合是表示一组对象的对象。集合框架是用于表示和操作集合的统一架构。”

简单来说,Java集合框架帮助您管理一组对象,并以高效和有组织的方式对其执行操作。它通过提供各种方法来处理对象组,使应用程序开发变得更加容易。使用Java集合框架可以有效地添加、删除、搜索和排序对象。

集合接口

在Java中,接口指定了任何实现它的类必须遵守的合同。这意味着实现类必须为接口中声明的所有方法提供具体实现。

在Java集合框架中,诸如SetListQueue等各种集合接口都扩展了Collection接口,并且它们必须遵守Collection接口定义的契约。

解读Java集合框架层次结构

查看这篇文章中展示Java集合层次结构的精美图表:

我们将从顶部开始逐步向下介绍,以便您了解这个图表展示的内容:

  1. 在Java集合框架的根部是Iterable接口,它允许您对集合的元素进行迭代。

  2. Collection接口扩展了Iterable接口。这意味着它继承了Iterable接口的属性和行为,并为添加、移除和检索元素添加了自己的行为。

  3. 诸如ListSetQueue之类的特定接口进一步扩展了Collection接口。这些接口中的每一个都有其他类来实现它们的方法。例如,ArrayListList接口的常用实现,HashSet实现了Set接口,诸如此类。

  4. Map接口是Java集合框架的一部分,但与上述其他接口不同,它并未扩展Collection接口。

  5. 该框架中的所有接口和类都属于java.util包。

注意:Java集合框架中常见的混淆点在于CollectionCollections之间的区别。Collection是框架中的一个接口,而Collections是一个实用类。Collections类提供了对集合元素执行操作的静态方法。

Java集合接口

到目前为止,您应该已经熟悉构成集合框架基础的不同类型集合。现在我们将更仔细地看一下ListSetQueueMap接口。

在本节中,我们将讨论每个接口,同时探索它们的内部机制。我们将研究它们如何处理重复元素,以及它们是否支持插入空值。我们还将了解插入期间元素的排序以及它们对同步的支持,涉及线程安全的概念。然后我们将逐个讨论这些接口的一些关键方法,并通过审查常见实现及其对各种操作的性能来结束。

在开始之前,让我们简要讨论一下同步和性能。

  • 同步通过多个线程控制对共享对象的访问,确保它们的完整性并防止冲突。这对于保持线程安全至关重要。

  • 在选择集合类型时,一个重要因素是其在常见操作(如插入、删除和检索)期间的性能。性能通常使用大O符号表示。您可以在这里了解更多信息。

列表

列表是一种有序或顺序集合。它遵循从零开始的索引,允许通过它们的索引位置插入、删除或访问元素。

  1. 内部机制:列表在内部由数组或链表支持,具体取决于实现类型。例如,ArrayList使用数组,而LinkedList在内部使用链表。您可以在这里阅读更多关于LinkedList的信息。列表在添加或删除元素时动态调整大小。基于索引的检索使其成为一种非常高效的集合类型。

  2. 重复项List中允许重复元素,这意味着List中可以有多个具有相同值的元素。可以根据存储的索引检索任何值。

  3. 空值List中也允许空值。由于允许重复,您也可以拥有多个空元素。

  4. 顺序:一个List维护插入顺序,意味着元素以添加的顺序存储。当您希望以插入顺序检索元素时,这将非常有用。

  5. 同步: 一个列表默认情况下不是同步的,这意味着它没有内置的处理同时被多个线程访问的方式。

  6. 关键方法:这是一个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
        }
    }
    
  7. 常见实现: ArrayList, LinkedList, Vector, Stack

  8. 性能: 通常,在ArrayListLinkedList中,插入和删除操作都很快。但获取元素可能会很慢,因为你必须遍历节点。

操作 ArrayList LinkedList
插入 在末尾快 – 摊销O(1),在开头或中间慢 – O(n) 在开头或中间快 – O(1),在末尾慢 – O(n)
删除 在末尾快 – 摊销O(1),在开头或中间慢 – O(n) 快 – 如果位置已知,则为O(1)
检索 快 – 对于随机访问为O(1) 慢 – 对于随机访问为O(n),因为会涉及遍历

集合

Set是一种不允许重复元素的集合类型,代表数学集合的概念。

  1. 内部机制: 一个Set在内部由一个HashMap支持。根据实现类型的不同,它可以由HashMapLinkedHashMapTreeMap支持。我已经写了一篇关于HashMap内部工作原理的详细文章在这里。一定要去看一下。

  2. 重复项: 由于Set代表数学集合的概念,不允许重复元素。这确保了所有元素都是唯一的,保持了集合的完整性。

  3. Null:在Set中最多允许一个空值,因为不允许重复。但这不适用于TreeSet的实现,TreeSet不允许空值。

  4. Ordering:在Set中元素的顺序取决于实现的类型。

    • HashSet:顺序不保证,元素可以放在任何位置。

    • LinkedHashSet:这个实现会保持插入顺序,因此您可以按照插入顺序检索元素。

    • TreeSet:元素根据其自然顺序插入。或者,您可以通过指定自定义比较器来控制插入顺序。

  5. 同步:一个Set不是同步的,这意味着如果两个或多个线程同时尝试访问一个Set对象,则可能会遇到并发问题,如竞态条件,这可能会影响数据完整性

  6. 关键方法: 这里是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);
         }
     }
    
  7. 常见实现: HashSetLinkedHashSetTreeSet

  8. 性能: 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(先进先出)顺序进行。这意味着元素在一端添加,在另一端移除,因此添加到队列的第一个元素是第一个被移除的元素。

  1. 内部机制:队列的内部工作方式可以根据其特定实现而有所不同。

    • LinkedList – 使用双向链表存储元素,这意味着可以向前和向后遍历,实现灵活的操作。

    • PriorityQueue – 内部由二叉堆支持,非常高效用于检索操作。

    • ArrayDeque – 使用数组实现,随着元素的添加或删除而扩展或缩小。在这里,元素可以从队列的两端添加或删除。

  2. 重复项: 在Queue中,允许重复元素,允许插入相同值的多个实例

  3. 空值: 不能向Queue插入空值,因为按设计,Queue的某些方法返回null表示为空。为避免混淆,不允许空值。

  4. 排序:元素根据其自然顺序插入。或者,您可以通过指定自定义比较器来控制插入顺序。

  5. 同步:默认情况下,Queue 不是同步的。但是,您可以使用 ConcurrentLinkedQueueBlockingQueue 实现来实现线程安全。

  6. 关键方法:这是一个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);
         }
     }
    
  7. 常见实现: LinkedList, PriorityQueue, ArrayDeque

  8. 性能: 像LinkedListArrayDeque这样的实现通常对于添加和移除项目都很快。而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接口。

  1. 内部机制Map 在内部使用基于哈希概念的 HashTable 进行操作。我已经就这个主题撰写了一篇详细的 文章,请阅读以深入了解。

  2. 重复项Map 将数据存储为键值对。在这里,每个键都是唯一的,因此不允许重复键。但允许重复值。

  3. 空值:由于不允许重复键,一个 Map 只能有一个空键。由于允许重复值,可以有多个空值。在 TreeMap 实现中,键不能为 null,因为它基于键对元素进行排序。但是,允许空值。

  4. 排序: Map 的插入顺序取决于实现方式:

    • HashMap – 插入顺序不能保证,因为它们是根据哈希概念确定的。

    • LinkedHashMap – 保留插入顺序,您可以按照添加到集合中的顺序检索元素。

    • TreeMap – 元素根据其自然顺序插入。或者,您可以通过指定自定义比较器来控制插入顺序。

  5. 同步:默认情况下,Map 不是同步的。但是您可以使用 Collections.synchronizedMap()ConcurrentHashMap 实现来实现线程安全。

  6. 关键方法: 这里是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);
         }
     }
    
  7. 常见实现: HashMap, LinkedHashMap, TreeMap, Hashtable, ConcurrentHashMap

  8. 性能: 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 实用类具有几个有用的静态方法,可让您对集合元素执行常用操作。这些方法帮助您减少应用程序中的样板代码,并让您专注于业务逻辑。

以下是一些关键特性和方法,以及它们的简要介绍:

  1. 排序: Collections.sort(List<T>) – 该方法用于对列表中的元素按升序排序。

  2. 搜索: Collections.binarySearch(List<T>, key) – 该方法用于在排序列表中搜索特定元素并返回其索引。

  3. 倒序: Collections.reverse(List<T>) – 该方法用于颠倒列表中元素的顺序。

  4. 最小/最大操作: Collections.min(Collection<T>)Collections.max(Collection<T>) – 这些方法分别用于在集合中找到最小和最大的元素。

  5. 同步: Collections.synchronizedList(List<T>) – 该方法用于通过同步使列表线程安全。

  6. 不可修改的集合: 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上与我联系。