Skip to content

14.1 集合是什么?与数组区别

集合的概念

集合(Collection)是 Java 中用于存储和管理一组对象的容器。它是 Java 集合框架(Collection Framework)的核心部分,提供了多种数据结构和算法,用于处理不同类型的集合操作。

集合框架的层次结构

Java 集合框架主要由以下几个部分组成:

  1. Collection 接口:所有集合类的根接口,定义了集合的基本操作
  2. List 接口:有序集合,允许重复元素
  3. Set 接口:无序集合,不允许重复元素
  4. Map 接口:键值对集合,不允许重复键
  5. Queue 接口:队列,通常用于先进先出(FIFO)操作

集合与数组的区别

特性数组集合
长度固定长度,一旦创建无法改变动态长度,可以根据需要自动调整
存储类型只能存储相同类型的元素可以存储不同类型的元素(但通常建议存储相同类型)
类型可以存储基本数据类型和对象只能存储对象(基本数据类型会被自动装箱)
方法提供的方法较少,主要是数组操作提供了丰富的方法,如添加、删除、查找等
性能访问元素速度快,适合随机访问某些操作(如插入、删除)性能更好
安全性类型安全,编译时检查泛型集合提供类型安全

集合的优势

  1. 动态大小:集合的大小可以根据需要自动调整,不需要预先指定大小
  2. 丰富的方法:提供了大量用于操作元素的方法,如添加、删除、查找、排序等
  3. 类型安全:通过泛型,可以在编译时检查元素类型
  4. 多数据结构:提供了多种数据结构,如列表、集合、映射、队列等,适应不同的使用场景
  5. 工具类:提供了 Collections 等工具类,用于操作集合

集合的基本操作

1. 添加元素

java
Collection<String> collection = new ArrayList<>();
collection.add("Java");
collection.add("Python");
collection.add("C++");

2. 删除元素

java
collection.remove("Python");

3. 检查元素是否存在

java
boolean contains = collection.contains("Java");

4. 获取集合大小

java
int size = collection.size();

5. 清空集合

java
collection.clear();

6. 遍历集合

java
for (String element : collection) {
    System.out.println(element);
}

集合的分类

1. List 接口

List 是有序集合,允许重复元素。主要实现类有:

  • ArrayList:基于数组实现,随机访问速度快
  • LinkedList:基于链表实现,插入和删除操作速度快
  • Vector:线程安全的 ArrayList

示例:

java
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("Java"); // 允许重复元素

// 访问元素
String first = list.get(0);

// 修改元素
list.set(1, "C++");

// 遍历元素
for (String element : list) {
    System.out.println(element);
}

2. Set 接口

Set 是无序集合,不允许重复元素。主要实现类有:

  • HashSet:基于哈希表实现,查找速度快
  • LinkedHashSet:基于哈希表和链表实现,保持插入顺序
  • TreeSet:基于红黑树实现,元素有序

示例:

java
Set<String> set = new HashSet<>();
set.add("Java");
set.add("Python");
set.add("Java"); // 重复元素,不会被添加

// 遍历元素
for (String element : set) {
    System.out.println(element);
}

3. Map 接口

Map 是键值对集合,不允许重复键。主要实现类有:

  • HashMap:基于哈希表实现,查找速度快
  • LinkedHashMap:基于哈希表和链表实现,保持插入顺序
  • TreeMap:基于红黑树实现,键有序
  • Hashtable:线程安全的 HashMap

示例:

java
Map<String, Integer> map = new HashMap<>();
map.put("Java", 1);
map.put("Python", 2);
map.put("C++", 3);

// 获取值
int value = map.get("Java");

// 遍历键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

4. Queue 接口

Queue 是队列,通常用于先进先出(FIFO)操作。主要实现类有:

  • LinkedList:实现了 Queue 接口
  • PriorityQueue:优先队列,元素按优先级排序

示例:

java
Queue<String> queue = new LinkedList<>();
queue.offer("Java");
queue.offer("Python");
queue.offer("C++");

// 出队
String element = queue.poll();

// 查看队首元素
String head = queue.peek();

// 遍历队列
for (String item : queue) {
    System.out.println(item);
}

集合的使用场景

1. List 的使用场景

  • 需要保持元素的插入顺序
  • 需要通过索引访问元素
  • 允许重复元素
  • 适用于需要频繁访问元素的场景

2. Set 的使用场景

  • 不需要保持元素的插入顺序
  • 不允许重复元素
  • 适用于需要快速查找元素的场景

3. Map 的使用场景

  • 需要通过键快速查找值
  • 键值对映射关系
  • 适用于需要键值映射的场景

4. Queue 的使用场景

  • 需要先进先出(FIFO)操作
  • 适用于任务队列、消息队列等场景

示例:集合的基本使用

示例 1:List 的使用

java
import java.util.ArrayList;
import java.util.List;

public class ListExample {
    public static void main(String[] args) {
        // 创建 List 集合
        List<String> languages = new ArrayList<>();
        
        // 添加元素
        languages.add("Java");
        languages.add("Python");
        languages.add("C++");
        languages.add("Java"); // 允许重复元素
        
        // 打印集合
        System.out.println("集合元素: " + languages);
        
        // 获取元素
        String firstLanguage = languages.get(0);
        System.out.println("第一个元素: " + firstLanguage);
        
        // 修改元素
        languages.set(1, "JavaScript");
        System.out.println("修改后: " + languages);
        
        // 删除元素
        languages.remove(2);
        System.out.println("删除后: " + languages);
        
        // 检查元素是否存在
        boolean contains = languages.contains("Java");
        System.out.println("是否包含 Java: " + contains);
        
        // 获取集合大小
        int size = languages.size();
        System.out.println("集合大小: " + size);
        
        // 遍历集合
        System.out.println("遍历集合:");
        for (String language : languages) {
            System.out.println("- " + language);
        }
        
        // 清空集合
        languages.clear();
        System.out.println("清空后: " + languages);
        System.out.println("清空后大小: " + languages.size());
    }
}

示例 2:Set 的使用

java
import java.util.HashSet;
import java.util.Set;

public class SetExample {
    public static void main(String[] args) {
        // 创建 Set 集合
        Set<String> languages = new HashSet<>();
        
        // 添加元素
        languages.add("Java");
        languages.add("Python");
        languages.add("C++");
        languages.add("Java"); // 重复元素,不会被添加
        
        // 打印集合
        System.out.println("集合元素: " + languages);
        
        // 检查元素是否存在
        boolean contains = languages.contains("Java");
        System.out.println("是否包含 Java: " + contains);
        
        // 删除元素
        languages.remove("Python");
        System.out.println("删除后: " + languages);
        
        // 获取集合大小
        int size = languages.size();
        System.out.println("集合大小: " + size);
        
        // 遍历集合
        System.out.println("遍历集合:");
        for (String language : languages) {
            System.out.println("- " + language);
        }
        
        // 清空集合
        languages.clear();
        System.out.println("清空后: " + languages);
        System.out.println("清空后大小: " + languages.size());
    }
}

示例 3:Map 的使用

java
import java.util.HashMap;
import java.util.Map;

public class MapExample {
    public static void main(String[] args) {
        // 创建 Map 集合
        Map<String, Integer> scores = new HashMap<>();
        
        // 添加键值对
        scores.put("Java", 95);
        scores.put("Python", 85);
        scores.put("C++", 90);
        
        // 打印集合
        System.out.println("集合元素: " + scores);
        
        // 获取值
        int javaScore = scores.get("Java");
        System.out.println("Java 分数: " + javaScore);
        
        // 修改值
        scores.put("Python", 92);
        System.out.println("修改后: " + scores);
        
        // 检查键是否存在
        boolean containsKey = scores.containsKey("C++");
        System.out.println("是否包含 C++: " + containsKey);
        
        // 删除键值对
        scores.remove("C++");
        System.out.println("删除后: " + scores);
        
        // 获取集合大小
        int size = scores.size();
        System.out.println("集合大小: " + size);
        
        // 遍历键值对
        System.out.println("遍历集合:");
        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        
        // 遍历键
        System.out.println("遍历键:");
        for (String key : scores.keySet()) {
            System.out.println("- " + key);
        }
        
        // 遍历值
        System.out.println("遍历值:");
        for (int value : scores.values()) {
            System.out.println("- " + value);
        }
        
        // 清空集合
        scores.clear();
        System.out.println("清空后: " + scores);
        System.out.println("清空后大小: " + scores.size());
    }
}

示例 4:Queue 的使用

java
import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        // 创建 Queue 集合
        Queue<String> queue = new LinkedList<>();
        
        // 添加元素(入队)
        queue.offer("Java");
        queue.offer("Python");
        queue.offer("C++");
        
        // 打印集合
        System.out.println("队列元素: " + queue);
        
        // 查看队首元素
        String head = queue.peek();
        System.out.println("队首元素: " + head);
        
        // 出队
        String element = queue.poll();
        System.out.println("出队元素: " + element);
        System.out.println("出队后: " + queue);
        
        // 获取队列大小
        int size = queue.size();
        System.out.println("队列大小: " + size);
        
        // 遍历队列
        System.out.println("遍历队列:");
        for (String item : queue) {
            System.out.println("- " + item);
        }
        
        // 清空队列
        queue.clear();
        System.out.println("清空后: " + queue);
        System.out.println("清空后大小: " + queue.size());
    }
}

常见问题

1. 集合的类型安全

症状:向集合中添加错误类型的元素,导致运行时异常

解决方案:使用泛型来确保类型安全

示例

java
// 错误:没有使用泛型
List list = new ArrayList();
list.add("Java");
list.add(123); // 可以添加不同类型的元素

// 正确:使用泛型
List<String> list = new ArrayList<>();
list.add("Java");
// list.add(123); // 编译错误,类型不匹配

2. 集合的性能问题

症状:集合操作速度慢

解决方案:根据具体需求选择合适的集合实现

示例

java
// 需要频繁随机访问元素时,使用 ArrayList
List<String> list = new ArrayList<>();

// 需要频繁插入和删除元素时,使用 LinkedList
List<String> list = new LinkedList<>();

// 需要快速查找元素时,使用 HashSet
Set<String> set = new HashSet<>();

// 需要键值映射时,使用 HashMap
Map<String, Integer> map = new HashMap<>();

3. 集合的线程安全问题

症状:在多线程环境中,集合操作导致数据不一致

解决方案:使用线程安全的集合实现,或使用同步机制

示例

java
// 线程安全的集合
List<String> list = Collections.synchronizedList(new ArrayList<>());
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<>());

// 或使用 Concurrent 集合
List<String> list = new CopyOnWriteArrayList<>();
Set<String> set = new ConcurrentHashSet<>();
Map<String, Integer> map = new ConcurrentHashMap<>();

最佳实践

  1. 选择合适的集合:根据具体需求选择合适的集合实现

  2. 使用泛型:使用泛型确保类型安全,避免运行时异常

  3. 考虑性能:根据操作类型选择合适的集合实现

  4. 注意线程安全:在多线程环境中,使用线程安全的集合或同步机制

  5. 合理使用工具类:使用 Collections 等工具类简化集合操作

  6. 避免不必要的装箱拆箱:对于基本数据类型,考虑使用原始类型的集合(如 IntArrayList)

  7. 注意集合的容量:对于 ArrayList 等有初始容量的集合,合理设置初始容量,减少扩容操作

总结

集合是 Java 中用于存储和管理一组对象的容器,与数组相比具有以下优势:

  1. 动态大小:集合的大小可以根据需要自动调整
  2. 丰富的方法:提供了大量用于操作元素的方法
  3. 类型安全:通过泛型,可以在编译时检查元素类型
  4. 多数据结构:提供了多种数据结构,适应不同的使用场景

Java 集合框架主要包括:

  • List:有序集合,允许重复元素
  • Set:无序集合,不允许重复元素
  • Map:键值对集合,不允许重复键
  • Queue:队列,通常用于先进先出操作

通过合理选择和使用集合,可以提高代码的效率和可维护性。

© 2026 编程马·菜鸟教程 版权所有