一、集合概述(前置基础)
集合是Java中用于存储多个数据的容器,区别于数组(固定长度、只能存储同一种基本类型/引用类型),集合长度可变,可存储不同类型的对象(本质存储对象引用),核心接口是Collection,List和Set是Collection的两大核心子接口。
核心特点:集合只存储对象,不存储基本数据类型(需使用包装类,如int→Integer);长度可动态增减;提供了丰富的方法(添加、删除、遍历、查找等),简化数据操作。
Collection核心通用方法(List和Set均适用):
add(E e):添加单个元素,返回boolean(添加成功为true);
remove(Object o):删除指定元素,返回boolean;
size():返回集合中元素的个数;
isEmpty():判断集合是否为空;
clear():清空集合中所有元素;
contains(Object o):判断集合中是否包含指定元素。
二、List集合
1. 核心定义与特点
List是Collection的子接口,代表有序、可重复的集合,元素有明确的索引(类似数组的下标),可通过索引快速访问、插入、删除元素,适合需要“有序存储、可重复、按索引操作”的场景。
核心特点:有序(元素存入顺序与取出顺序一致)、可重复(允许存储多个相同的元素)、有索引(从0开始,依次递增)。
2. List接口的常用实现类(3个核心)
(1)ArrayList(最常用)
底层基于数组实现,查询效率高(通过索引直接访问,时间复杂度O(1)),增删效率低(需移动数组元素,时间复杂度O(n)),线程不安全,效率高,适合“查询频繁、增删较少”的场景(如展示列表数据)。
// ArrayList示例 List<String> list = new ArrayList<>(); // 添加元素 list.add("Java"); list.add("Python"); list.add("Java"); // 允许重复 // 按索引插入元素(指定位置插入) list.add(1, "C++"); // 插入后:[Java, C++, Python, Java] // 按索引访问元素 String element = list.get(0); // 结果:Java // 按索引修改元素 list.set(2, "JavaScript"); // 修改后:[Java, C++, JavaScript, Java] // 按索引删除元素 list.remove(3); // 删除索引3的元素,返回删除的元素 // 遍历集合(三种方式) // 1. 普通for循环(利用索引,最常用) for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } // 2. 增强for循环(foreach,无需索引) for (String str : list) { System.out.println(str); } // 3. 迭代器遍历(安全遍历,可在遍历中删除元素) Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String str = iterator.next(); if ("C++".equals(str)) { iterator.remove(); // 迭代器删除,避免并发修改异常 } }(2)LinkedList
底层基于双向链表实现,查询效率低(需从头/尾遍历查找,时间复杂度O(n)),增删效率高(只需修改链表节点的指针,时间复杂度O(1)),线程不安全,适合“增删频繁、查询较少”的场景(如队列、栈的实现)。
额外特性:LinkedList还实现了Deque接口,可作为队列(先进先出)、栈(先进后出)使用,提供了poll()、push()、pop()等方法。
(3)Vector
底层基于数组实现,与ArrayList功能基本一致,但线程安全(方法加了synchronized锁),效率低,目前已基本被ArrayList替代,仅在多线程场景下偶尔使用。
3. List集合的特有方法(区别于Set)
因List有索引,所以拥有Set没有的、基于索引的操作方法:
get(int index):获取指定索引的元素;
set(int index, E e):修改指定索引的元素,返回被修改的旧元素;
add(int index, E e):在指定索引插入元素,后续元素后移;
remove(int index):删除指定索引的元素,返回被删除的元素;
indexOf(Object o):返回指定元素在集合中第一次出现的索引,没有则返回-1;
lastIndexOf(Object o):返回指定元素在集合中最后一次出现的索引,没有则返回-1。
4. List集合的注意事项
ArrayList和LinkedList均线程不安全,多线程环境下需手动加锁,或使用Collections.synchronizedList()包装;
遍历ArrayList时,优先使用普通for循环(效率高);遍历LinkedList时,优先使用增强for或迭代器(避免频繁通过索引查找);
避免在增强for循环中修改集合(添加/删除元素),会抛出ConcurrentModificationException(并发修改异常),需使用迭代器删除。
三、Set集合
1. 核心定义与特点
Set是Collection的子接口,代表无序、不可重复的集合,元素没有索引,无法通过索引访问元素,适合需要“去重存储、无需按顺序访问”的场景(如存储唯一标识、去重数据)。
核心特点:无序(元素存入顺序与取出顺序不一定一致,底层存储无序)、不可重复(不允许存储两个相等的元素,equals()方法判断相等)、无索引(无法通过下标访问)。
补充:Set判断元素是否重复的规则:先通过hashCode()方法判断哈希值,若哈希值不同,则元素不同;若哈希值相同,再通过equals()方法判断,若equals()返回true,则元素重复,不添加;若返回false,则添加。
2. Set接口的常用实现类(3个核心)
(1)HashSet(最常用)
底层基于哈希表(HashMap)实现,无序、不可重复,查询和增删效率都很高(时间复杂度O(1)),线程不安全,适合“去重、高效操作”的常规场景。
// HashSet示例 Set<String> set = new HashSet<>(); // 添加元素(不可重复,重复元素添加失败) set.add("Java"); set.add("Python"); set.add("Java"); // 重复元素,添加失败,集合中仍只有1个Java // 删除元素 set.remove("Python"); // 删除成功返回true,不存在返回false // 遍历集合(两种方式,无索引,无法用普通for循环) // 1. 增强for循环 for (String str : set) { System.out.println(str); // 输出顺序可能与添加顺序不一致 } // 2. 迭代器遍历 Iterator<String> iterator = set.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } // 判断元素是否存在 boolean hasJava = set.contains("Java"); // 结果:true(2)LinkedHashSet
底层基于哈希表+双向链表实现,是HashSet的子类,特点:有序、不可重复(有序指“存入顺序与取出顺序一致”),查询和增删效率略低于HashSet,线程不安全,适合“去重且需要保持插入顺序”的场景。
(3)TreeSet
底层基于红黑树实现,无序(不保证插入顺序)、不可重复,但会对元素进行自然排序(默认升序),也可自定义排序规则,查询和增删效率中等(时间复杂度O(log n)),线程不安全,适合“去重且需要排序”的场景。
// TreeSet示例(自然排序,String类型默认按字典序升序) Set<String> treeSet = new TreeSet<>(); treeSet.add("Banana"); treeSet.add("Apple"); treeSet.add("Cherry"); // 遍历输出:Apple、Banana、Cherry(自然排序后) for (String str : treeSet) { System.out.println(str); } // 自定义排序(如整数降序) Set<Integer> numSet = new TreeSet<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; // 降序排序 } }); numSet.add(3); numSet.add(1); numSet.add(2); // 遍历输出:3、2、13. Set集合的注意事项
HashSet、LinkedHashSet、TreeSet均线程不安全,多线程环境下需使用Collections.synchronizedSet()包装;
HashSet存储自定义对象时,需重写hashCode()和equals()方法,否则无法实现去重(默认使用Object类的方法,判断地址是否相同);
TreeSet存储自定义对象时,需让对象实现Comparable接口(重写compareTo()方法),或创建TreeSet时传入Comparator,否则会抛出ClassCastException(类型转换异常);
Set无索引,无法使用普通for循环遍历,只能用增强for、迭代器遍历。
四、List集合与Set集合的核心区别(重点)
| 对比维度 | List集合 | Set集合 |
|---|---|---|
| 有序性 | 有序(存入与取出顺序一致) | 无序(HashSet、TreeSet);LinkedHashSet有序 |
| 可重复性 | 可重复 | 不可重复 |
| 索引 | 有索引,可通过索引操作 | 无索引,无法通过索引操作 |
| 底层实现 | ArrayList(数组)、LinkedList(链表) | HashSet(哈希表)、TreeSet(红黑树) |
| 适用场景 | 需有序、可重复、按索引操作(如列表展示) | 需去重、无需索引(如唯一标识存储) |
五、核心总结
List和Set均继承自Collection,拥有Collection的所有通用方法;
List核心:有序、可重复、有索引,重点掌握ArrayList和LinkedList的区别;
Set核心:不可重复、无索引,重点掌握HashSet(常规去重)、TreeSet(排序去重)的使用;
选型原则:需有序/按索引操作→用List;需去重→用Set;需排序去重→TreeSet;需保持插入顺序去重→LinkedHashSet。