1. 集合框架概述
1.1 什么是集合框架?
集合框架是一个统一的架构,用于表示和操作集合,使集合能够独立于其实现细节进行操作。Java集合框架提供了一系列接口和类,用于存储、检索、操作和传输数据对象。
1.2 为什么需要集合框架?
在Java早期版本中,Java提供了有限的几个类用于数据存储和操作(如Vector、Hashtable等),但这些类缺乏统一的设计理念。Java 1.2引入集合框架,目的是:
提供高性能、高质量的数据结构和算法实现减少编程工作量,通过提供现成的数据结构提供接口和实现分离的设计,使代码更具可重用性和互操作性建立通用语言,方便开发者交流算法和数据结构
1.3 集合框架的组成部分
Java集合框架主要由三部分组成:
接口(Interfaces):表示集合的抽象数据类型。允许集合独立于其实现细节进行操作。实现类(Implementations):接口的具体实现,即可重用的数据结构。算法(Algorithms):对实现了集合接口的对象进行操作的方法,如搜索和排序。
2. 核心接口
Java集合框架的核心接口构成了框架的基础。以下是最重要的接口:
2.1 Collection接口
Collection是集合层次结构的根接口,所有集合类都实现了这个接口。它提供了适用于所有集合的基本操作。
主要方法:
boolean add(E e) - 添加元素boolean remove(Object o) - 移除元素boolean contains(Object o) - 检查是否包含指定元素int size() - 返回集合中的元素数量boolean isEmpty() - 检查集合是否为空Iterator
2.2 List接口
List是一个有序的Collection,用户可以通过索引访问元素,并且允许重复元素。
特点:
有序(插入顺序)允许重复元素可以通过索引访问元素
额外的主要方法:
E get(int index) - 获取指定位置的元素E set(int index, E element) - 替换指定位置的元素void add(int index, E element) - 在指定位置插入元素E remove(int index) - 移除指定位置的元素int indexOf(Object o) - 返回指定元素第一次出现的索引int lastIndexOf(Object o) - 返回指定元素最后一次出现的索引List
2.3 Set接口
Set是一个不包含重复元素的Collection。
特点:
不允许重复元素大多数实现类不保证元素的顺序有些实现类可能保证特定的顺序(如LinkedHashSet维护插入顺序)
Set接口没有在Collection接口之外添加新方法,只是改变了一些方法的行为语义。例如,add方法对于已存在的元素将返回false。
2.4 Queue接口
Queue代表了先进先出(FIFO)的数据结构,除了基本的Collection操作外,还提供了额外的插入、提取和检查操作。
主要方法:
boolean offer(E e) - 将元素添加到队列(不抛出异常)E poll() - 获取并移除队首元素,如果队列为空则返回nullE peek() - 获取但不移除队首元素,如果队列为空则返回nullE element() - 获取但不移除队首元素,如果队列为空则抛出异常E remove() - 获取并移除队首元素,如果队列为空则抛出异常
2.5 Deque接口
Deque(双端队列)是Queue的子接口,允许在两端进行插入和删除操作。
主要方法:
void addFirst(E e) / void addLast(E e) - 在队列前端/后端添加元素E removeFirst() / E removeLast() - 移除并返回队列前端/后端的元素E getFirst() / E getLast() - 获取但不移除队列前端/后端的元素boolean offerFirst(E e) / boolean offerLast(E e) - 在队列前端/后端添加元素(不抛出异常)E pollFirst() / E pollLast() - 获取并移除队列前端/后端的元素,如果队列为空则返回nullE peekFirst() / E peekLast() - 获取但不移除队列前端/后端的元素,如果队列为空则返回null
2.6 Map接口
Map虽然不是Collection的子接口,但也是集合框架的一部分。Map将键映射到值,不能包含重复的键,每个键最多映射到一个值。
主要方法:
V put(K key, V value) - 添加键值对V get(Object key) - 获取指定键对应的值V remove(Object key) - 移除指定键对应的值boolean containsKey(Object key) - 检查是否包含指定键boolean containsValue(Object value) - 检查是否包含指定值Set
3. 实现类
Java集合框架提供了许多实现类,每个类都有其特定的特性和适用场景。
3.1 List实现
3.1.1 ArrayList
ArrayList是基于数组实现的List,提供了快速的随机访问,但插入和删除元素的速度较慢。
特点:
基于动态数组实现随机访问元素的时间复杂度为O(1)在末尾添加元素的平均时间复杂度为O(1)在中间插入/删除元素的时间复杂度为O(n)非同步(线程不安全)
示例:
List
arrayList.add("Java");
arrayList.add("Python");
arrayList.add("C++");
System.out.println(arrayList.get(1)); // 输出: Python
3.1.2 LinkedList
LinkedList是基于双向链表实现的List,提供了快速的插入和删除操作,但随机访问元素的速度较慢。
特点:
基于双向链表实现随机访问元素的时间复杂度为O(n)插入/删除元素的时间复杂度为O(1)(假设已知位置)同时实现了List和Deque接口非同步(线程不安全)
示例:
LinkedList
linkedList.add("Java");
linkedList.add("Python");
linkedList.addFirst("C++"); // 在开头添加元素
linkedList.addLast("JavaScript"); // 在末尾添加元素
System.out.println(linkedList); // 输出: [C++, Java, Python, JavaScript]
3.1.3 Vector
Vector是早期Java版本中的集合类,现在已经被重新设计为实现List接口。它类似于ArrayList,但所有方法都是同步的。
特点:
基于动态数组实现所有方法都是同步的(线程安全)性能比ArrayList稍差现在一般不推荐使用,除非需要线程安全
示例:
Vector
vector.add("Java");
vector.add("Python");
vector.add("C++");
System.out.println(vector.elementAt(1)); // 输出: Python
3.1.4 Stack
Stack继承自Vector,实现了标准的后进先出(LIFO)栈。
特点:
继承自Vector,所以是线程安全的表示后进先出(LIFO)的数据结构现在通常推荐使用Deque接口的实现类代替Stack
主要方法:
E push(E item) - 将元素压入栈顶E pop() - 移除并返回栈顶元素E peek() - 获取但不移除栈顶元素boolean empty() - 检查栈是否为空int search(Object o) - 返回元素在栈中的位置
示例:
Stack
stack.push("Java");
stack.push("Python");
stack.push("C++");
System.out.println(stack.pop()); // 输出: C++
System.out.println(stack.peek()); // 输出: Python
3.2 Set实现
3.2.1 HashSet
HashSet是基于哈希表实现的Set,它不保证集合的迭代顺序,允许使用null元素。
特点:
基于HashMap实现不保证元素的顺序允许null元素提供最佳的性能非同步(线程不安全)
示例:
Set
hashSet.add("Java");
hashSet.add("Python");
hashSet.add("Java"); // 重复元素,不会被添加
System.out.println(hashSet); // 输出顺序可能不同
3.2.2 LinkedHashSet
LinkedHashSet是HashSet的子类,它维护了一个双向链表,记录了元素的插入顺序。
特点:
基于LinkedHashMap实现维护元素的插入顺序允许null元素性能略低于HashSet非同步(线程不安全)
示例:
Set
linkedHashSet.add("Java");
linkedHashSet.add("Python");
linkedHashSet.add("C++");
System.out.println(linkedHashSet); // 输出: [Java, Python, C++]
3.2.3 TreeSet
TreeSet是基于红黑树实现的NavigableSet,它按照元素的自然顺序或者指定的比较器排序。
特点:
基于TreeMap实现元素按照自然顺序或指定的比较器排序不允许null元素提供了很多额外的方法来处理有序集合性能比HashSet和LinkedHashSet差非同步(线程不安全)
示例:
TreeSet
treeSet.add("Java");
treeSet.add("Python");
treeSet.add("C++");
System.out.println(treeSet); // 输出: [C++, Java, Python](按字母顺序)
// 使用比较器
TreeSet
customTreeSet.add("Java");
customTreeSet.add("Python");
customTreeSet.add("C++");
System.out.println(customTreeSet); // 输出: [Python, Java, C++](按字母逆序)
3.3 Queue实现
3.3.1 PriorityQueue
PriorityQueue是基于优先级堆实现的队列,元素按照自然顺序或者指定的比较器排序。
特点:
基于优先级堆(通常是二叉堆)实现元素按照自然顺序或指定的比较器排序不允许null元素非同步(线程不安全)
示例:
PriorityQueue
priorityQueue.add(5);
priorityQueue.add(1);
priorityQueue.add(3);
System.out.println(priorityQueue.poll()); // 输出: 1
System.out.println(priorityQueue.poll()); // 输出: 3
3.3.2 ArrayDeque
ArrayDeque是基于可调整大小的数组实现的双端队列,它作为栈和队列都比Stack和LinkedList更高效。
特点:
基于循环数组实现没有容量限制不允许null元素作为栈使用时比Stack更快作为队列使用时比LinkedList更快非同步(线程不安全)
示例:
// 作为队列使用
Queue
queue.offer("Java");
queue.offer("Python");
queue.offer("C++");
System.out.println(queue.poll()); // 输出: Java
// 作为栈使用
Deque
stack.push("Java");
stack.push("Python");
stack.push("C++");
System.out.println(stack.pop()); // 输出: C++
3.4 Map实现
3.4.1 HashMap
HashMap是基于哈希表实现的Map,它不保证映射的顺序,允许使用null键和null值。
特点:
基于哈希表实现不保证映射的顺序允许一个null键和多个null值提供最佳的性能非同步(线程不安全)
示例:
Map
hashMap.put("Java", 1995);
hashMap.put("Python", 1991);
hashMap.put("C++", 1983);
System.out.println(hashMap.get("Java")); // 输出: 1995
3.4.2 LinkedHashMap
LinkedHashMap是HashMap的子类,它维护了一个双向链表,可以记录元素的插入顺序或访问顺序。
特点:
基于HashMap和双向链表实现维护元素的插入顺序或访问顺序(可配置)允许一个null键和多个null值性能略低于HashMap非同步(线程不安全)
示例:
// 按插入顺序
Map
linkedHashMap.put("Java", 1995);
linkedHashMap.put("Python", 1991);
linkedHashMap.put("C++", 1983);
System.out.println(linkedHashMap); // 输出保持插入顺序
// 按访问顺序(LRU缓存)
Map
lruCache.put("Java", 1995);
lruCache.put("Python", 1991);
lruCache.put("C++", 1983);
lruCache.get("Java"); // 访问Java
System.out.println(lruCache); // Java将移到最后(最近访问)
3.4.3 TreeMap
TreeMap是基于红黑树实现的NavigableMap,它按照键的自然顺序或者指定的比较器排序。
特点:
基于红黑树实现键按照自然顺序或指定的比较器排序不允许null键,但允许多个null值提供了很多额外的方法来处理有序映射性能比HashMap和LinkedHashMap差非同步(线程不安全)
示例:
TreeMap
treeMap.put("Java", 1995);
treeMap.put("Python", 1991);
treeMap.put("C++", 1983);
System.out.println(treeMap); // 输出按键的字母顺序排序
// 使用比较器
TreeMap
customTreeMap.put("Java", 1995);
customTreeMap.put("Python", 1991);
customTreeMap.put("C++", 1983);
System.out.println(customTreeMap); // 输出按键的字母逆序排序
3.4.4 Hashtable
Hashtable是早期Java版本中的类,现在已经被重新设计为实现Map接口。它类似于HashMap,但所有方法都是同步的,并且不允许null键或值。
特点:
基于哈希表实现所有方法都是同步的(线程安全)不允许null键或值性能比HashMap差现在一般不推荐使用,除非需要线程安全
示例:
Hashtable
hashtable.put("Java", 1995);
hashtable.put("Python", 1991);
hashtable.put("C++", 1983);
System.out.println(hashtable.get("Java")); // 输出: 1995
3.4.5 Properties
Properties是Hashtable的子类,用于存储字符串键值对。它通常用于读取和写入配置文件。
特点:
继承自Hashtable,所以是线程安全的键和值都是字符串可以从流中加载和保存提供了默认值的功能
示例:
Properties properties = new Properties();
properties.setProperty("username", "admin");
properties.setProperty("password", "123456");
System.out.println(properties.getProperty("username")); // 输出: admin
System.out.println(properties.getProperty("email", "default@example.com")); // 输出默认值: default@example.com
// 保存到文件
try (FileOutputStream out = new FileOutputStream("config.properties")) {
properties.store(out, "Configuration");
} catch (IOException e) {
e.printStackTrace();
}
// 从文件加载
Properties loadedProps = new Properties();
try (FileInputStream in = new FileInputStream("config.properties")) {
loadedProps.load(in);
System.out.println(loadedProps.getProperty("username")); // 输出: admin
} catch (IOException e) {
e.printStackTrace();
}
4. 工具类
Java集合框架提供了两个主要的工具类,用于对集合和数组进行操作。
4.1 Collections类
Collections类提供了一系列静态方法,用于操作和返回集合。
主要方法:
排序
sort(List
binarySearch(List extends Comparable super T>> list, T key) - 使用二分查找算法查找元素max(Collection extends T> coll) - 返回最大元素min(Collection extends T> coll) - 返回最小元素frequency(Collection> c, Object o) - 返回指定元素在集合中出现的次数 修改
reverse(List> list) - 反转列表shuffle(List> list) - 随机打乱列表swap(List> list, int i, int j) - 交换列表中的两个元素fill(List super T> list, T obj) - 用指定元素替换列表中的所有元素copy(List super T> dest, List extends T> src) - 复制列表rotate(List> list, int distance) - 旋转列表replaceAll(List
unmodifiableXXX(XXX extends T> c) - 返回不可修改的视图synchronizedXXX(XXX
示例:
List
numbers.add(3);
numbers.add(1);
numbers.add(2);
// 排序
Collections.sort(numbers);
System.out.println(numbers); // 输出: [1, 2, 3]
// 二分查找
int index = Collections.binarySearch(numbers, 2);
System.out.println(index); // 输出: 1
// 反转
Collections.reverse(numbers);
System.out.println(numbers); // 输出: [3, 2, 1]
// 打乱
Collections.shuffle(numbers);
System.out.println(numbers); // 输出随机顺序
// 不可修改的集合
List
try {
unmodifiableList.add(4); // 抛出UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify unmodifiable list");
}
// 同步集合
List
// 单例集合
Set
4.2 Arrays类
Arrays类提供了一系列静态方法,用于操作数组。
主要方法:
排序
sort(T[] a) - 使用自然顺序对数组进行排序sort(T[] a, Comparator super T> c) - 使用比较器对数组进行排序parallelSort(T[] a) - 使用并行排序算法对数组进行排序 查找
binarySearch(T[] a, T key) - 使用二分查找算法查找元素 比较和填充
equals(T[] a, T[] a2) - 比较两个数组是否相等fill(T[] a, T val) - 用指定值填充数组copyOf(T[] original, int newLength) - 复制数组copyOfRange(T[] original, int from, int to) - 复制数组的指定范围 转换
asList(T... a) - 返回由指定数组支持的固定大小的列表stream(T[] array) - 返回由数组支持的顺序Stream 其他
toString(T[] a) - 返回数组的字符串表示形式deepToString(Object[] a) - 返回多维数组的字符串表示形式hashCode(T[] a) - 返回数组的哈希码deepHashCode(Object[] a) - 返回多维数组的哈希码setAll(T[] array, IntFunction extends T> generator) - 使用生成器函数设置数组的所有元素
示例:
Integer[] numbers = {3, 1, 2};
// 排序
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers)); // 输出: [1, 2, 3]
// 二分查找
int index = Arrays.binarySearch(numbers, 2);
System.out.println(index); // 输出: 1
// 填充
Arrays.fill(numbers, 0);
System.out.println(Arrays.toString(numbers)); // 输出: [0, 0, 0]
// 复制
Integer[] original = {1, 2, 3};
Integer[] copy = Arrays.copyOf(original, 5);
System.out.println(Arrays.toString(copy)); // 输出: [1, 2, 3, null, null]
// 转换为List
List
System.out.println(list); // 输出: [1, 2, 3]
// 注意: 返回的列表是固定大小的,不能添加或删除元素
// 多维数组
Integer[][] matrix = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepToString(matrix)); // 输出: [[1, 2], [3, 4]]
5. 特殊集合
5.1 Legacy集合
Legacy集合是Java 1.2之前就存在的集合类,它们已经被重新设计为适应集合框架。
包括:
Vector - 线程安全的动态数组Stack - 继承自Vector的LIFO栈Hashtable - 线程安全的哈希表Properties - 继承自Hashtable的字符串键值对存储Enumeration
尽管这些类现在都是集合框架的一部分,但它们的设计与框架中的其他部分不太协调。一般情况下,应该使用它们的替代品(ArrayList、HashMap等)。
5.2 并发集合
Java提供了许多线程安全的集合实现,它们位于java.util.concurrent包中。
主要类:
ConcurrentHashMap - 高并发、高性能的线程安全HashMapCopyOnWriteArrayList - 线程安全的ArrayList,适用于读多写少的场景CopyOnWriteArraySet - 使用CopyOnWriteArrayList实现的线程安全SetConcurrentLinkedQueue - 线程安全的非阻塞队列ConcurrentLinkedDeque - 线程安全的非阻塞双端队列ConcurrentSkipListMap - 线程安全的NavigableMapConcurrentSkipListSet - 线程安全的NavigableSetArrayBlockingQueue - 基于数组的有界阻塞队列LinkedBlockingQueue - 基于链表的可选有界阻塞队列PriorityBlockingQueue - 支持优先级的无界阻塞队列DelayQueue - 延迟元素的无界阻塞队列LinkedTransferQueue - 基于链表的无界传输队列LinkedBlockingDeque - 基于链表的可选有界阻塞双端队列SynchronousQueue - 没有内部容量的阻塞队列
示例:
// ConcurrentHashMap
Map
concurrentMap.put("Java", 1995);
concurrentMap.put("Python", 1991);
System.out.println(concurrentMap.get("Java")); // 输出: 1995
// CopyOnWriteArrayList
List
copyOnWriteList.add("Java");
copyOnWriteList.add("Python");
// 适合多线程读操作多,写操作少的场景
// ArrayBlockingQueue
BlockingQueue
blockingQueue.put("Java"); // 如果队列已满,put方法会阻塞
String item = blockingQueue.take(); // 如果队列为空,take方法会阻塞
System.out.println(item); // 输出: Java
5.3 不可变集合
不可变集合是指创建后不能修改的集合。Java提供了几种方式来创建不可变集合:
Collections工具类
Collections.unmodifiableXXX(XXX
创建不可变的集合 Guava库的ImmutableXXX类
提供了更丰富的不可变集合操作
示例:
// 使用Collections
List
modifiableList.add("Java");
modifiableList.add("Python");
List
// 使用Java 9+的工厂方法
List
Set
Map
// 尝试修改不可变集合会抛出UnsupportedOperationException
try {
immutableList.add("Go");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify immutable list");
}
6. 集合操作
6.1 遍历集合
Java提供了多种方式来遍历集合:
使用Iterator
List
list.add("Java");
list.add("Python");
list.add("C++");
Iterator
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
// 安全地删除元素
if (element.equals("Python")) {
iterator.remove();
}
}
使用for-each循环
for (String element : list) {
System.out.println(element);
// 注意: 在for-each循环中不能安全地修改集合
}
使用索引(仅适用于List)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
使用forEach方法(Java 8+)
list.forEach(element -> System.out.println(element));
// 或使用方法引用
list.forEach(System.out::println);
使用Stream API(Java 8+)
list.stream()
.filter(element -> element.startsWith("J"))
.forEach(System.out::println);
6.2 排序集合
使用Collections.sort()
List
list.add("C++");
list.add("Python");
list.add("Java");
// 自然顺序排序
Collections.sort(list);
System.out.println(list); // 输出: [C++, Java, Python]
// 使用比较器排序
Collections.sort(list, Comparator.reverseOrder());
System.out.println(list); // 输出: [Python, Java, C++]
使用List.sort()(Java 8+)
// 自然顺序排序
list.sort(null);
System.out.println(list); // 输出: [C++, Java, Python]
// 使用比较器排序
list.sort(Comparator.reverseOrder());
System.out.println(list); // 输出: [Python, Java, C++]
使用TreeSet或TreeMap自动排序
Set
treeSet.add("C++");
treeSet.add("Python");
treeSet.add("Java");
System.out.println(treeSet); // 输出: [C++, Java, Python]
使用Stream API排序(Java 8+)
List
.sorted()
.collect(Collectors.toList());
System.out.println(sortedList); // 输出: [C++, Java, Python]
6.3 集合间的转换
Collection转数组
List
list.add("Java");
list.add("Python");
// 转换为Object数组
Object[] objectArray = list.toArray();
// 转换为指定类型的数组
String[] stringArray = list.toArray(new String[0]);
// 或使用Java 11+的新方法
String[] stringArray2 = list.toArray(String[]::new);
数组转Collection
String[] array = {"Java", "Python", "C++"};
// 使用Arrays.asList() - 返回固定大小的List
List
// 使用new ArrayList<>(Arrays.asList()) - 返回可调整大小的List
List
// 使用List.of() - 返回不可变List(Java 9+)
List
// 使用Stream API(Java 8+)
List
Set
Collection之间的转换
List
list.add("Java");
list.add("Python");
list.add("Java"); // 重复元素
// List转Set(移除重复元素)
Set
System.out.println(set); // 输出: [Java, Python]
// Set转List
List
// 使用Stream API
List
Set
Map与Collection的转换
Map
map.put("Java", 1995);
map.put("Python", 1991);
// 获取键的集合
Set
// 获取值的集合
Collection
// 获取键值对的集合
Set
// Collection转Map(使用Stream API)
List
Map
.collect(Collectors.toMap(
s -> s, // 键映射函数
String::length // 值映射函数
));
System.out.println(lengthMap); // 输出: {Java=4, C++=3, Python=6}
7. 高级主题
7.1 泛型与集合
Java泛型允许在编译时提供类型安全性,使集合更加类型安全。
示例:
// 不使用泛型(Java 5之前)
List list = new ArrayList();
list.add("Java");
list.add(1); // 可以添加任何类型的对象
String s = (String) list.get(0); // 需要强制类型转换
Integer i = (Integer) list.get(1); // 需要强制类型转换
// 使用泛型(Java 5+)
List
stringList.add("Java");
// stringList.add(1); // 编译错误,只能添加String类型
String s2 = stringList.get(0); // 不需要强制类型转换
泛型通配符:
> - 无界通配符,表示任何类型 extends T> - 上界通配符,表示T或T的子类型 super T> - 下界通配符,表示T或T的超类型
示例:
// 使用上界通配符
List extends Number> numbers = new ArrayList
// numbers.add(1); // 不合法,不能添加元素
Number n = numbers.get(0); // 合法,可以获取元素
// 使用下界通配符
List super Integer> integers = new ArrayList
integers.add(1); // 合法,可以添加Integer
// Integer i = integers.get(0); // 不合法,不能直接获取
Object o = integers.get(0); // 合法,可以获取为Object
PECS原则(Producer Extends, Consumer Super):
如果你只需要从集合中获取元素(生产者),使用? extends T如果你只需要向集合中添加元素(消费者),使用? super T
7.2.1 Comparable接口
Comparable接口允许类的对象进行自然排序。实现这个接口的类必须提供compareTo方法。
public class Student implements Comparable
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student other) {
// 按年龄升序排序
return this.age - other.age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
// 使用
List
students.add(new Student("Alice", 22));
students.add(new Student("Bob", 20));
students.add(new Student("Charlie", 25));
Collections.sort(students); // 使用自然顺序排序
System.out.println(students); // 按年龄升序输出
7.2.2 Comparator接口
Comparator接口提供了一种外部比较策略,允许在不修改类的情况下定义多种排序方式。
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
// 使用Comparator
List
students.add(new Student("Alice", 22));
students.add(new Student("Bob", 20));
students.add(new Student("Charlie", 25));
// 按年龄排序
Collections.sort(students, new Comparator
@Override
public int compare(Student s1, Student s2) {
return s1.getAge() - s2.getAge();
}
});
System.out.println(students); // 按年龄升序输出
// 使用Lambda表达式(Java 8+)
Collections.sort(students, (s1, s2) -> s1.getAge() - s2.getAge());
// 使用Comparator静态方法(Java 8+)
Collections.sort(students, Comparator.comparingInt(Student::getAge));
// 按姓名排序
Collections.sort(students, Comparator.comparing(Student::getName));
System.out.println(students); // 按姓名升序输出
// 组合比较器
Comparator
Comparator
.thenComparing(byAgeDesc);
students.sort(byNameThenAgeDesc);
7.3 自定义集合实现
尽管Java集合框架提供了丰富的实现,但有时您可能需要创建自定义的集合类。
创建自定义集合的方式:
扩展现有集合类
public class LoggingArrayList
private static final long serialVersionUID = 1L;
@Override
public boolean add(E e) {
System.out.println("Adding element: " + e);
return super.add(e);
}
@Override
public E remove(int index) {
E element = super.remove(index);
System.out.println("Removed element at index " + index + ": " + element);
return element;
}
}
实现集合接口
public class SimpleArrayList
private Object[] elements;
private int size;
public SimpleArrayList() {
elements = new Object[10];
size = 0;
}
@Override
public boolean add(E e) {
ensureCapacity();
elements[size++] = e;
return true;
}
@SuppressWarnings("unchecked")
@Override
public E get(int index) {
checkIndex(index);
return (E) elements[index];
}
@Override
public int size() {
return size;
}
// 省略其他必要的方法实现...
private void ensureCapacity() {
if (size == elements.length) {
elements = Arrays.copyOf(elements, size * 2);
}
}
private void checkIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
}
使用组合而非继承
public class CountingSet
private final Set
private int addCount = 0;
public CountingSet(Set
this.delegate = delegate;
}
@Override
public boolean add(E e) {
addCount++;
return delegate.add(e);
}
@Override
public boolean addAll(Collection extends E> c) {
addCount += c.size();
return delegate.addAll(c);
}
public int getAddCount() {
return addCount;
}
// 委托其他方法到delegate
@Override
public int size() {
return delegate.size();
}
// 省略其他委托方法...
}
8. 最佳实践与常见陷阱
8.1 选择合适的集合
选择集合时应考虑的因素:
需要存储的元素类型访问模式(随机访问 vs. 顺序访问)是否需要保持顺序是否允许重复元素是否需要线程安全性能需求
常见集合的选择指南:
需要快速随机访问:ArrayList频繁在中间插入/删除元素:LinkedList不允许重复元素,没有特定顺序要求:HashSet不允许重复元素,需要维护插入顺序:LinkedHashSet不允许重复元素,需要元素保持排序:TreeSet需要键值对映射,没有特定顺序要求:HashMap需要键值对映射,需要维护插入顺序:LinkedHashMap需要键值对映射,需要键保持排序:TreeMap需要FIFO队列:LinkedList或ArrayDeque需要LIFO栈:ArrayDeque需要优先级队列:PriorityQueue需要线程安全:考虑java.util.concurrent包中的集合
8.2 性能考虑
各种集合操作的时间复杂度:
集合类型添加删除获取包含迭代ArrayListO(1)*O(n)O(1)O(n)O(n)LinkedListO(1)O(1)*O(n)O(n)O(n)HashSetO(1)O(1)N/AO(1)O(n)LinkedHashSetO(1)O(1)N/AO(1)O(n)TreeSetO(log n)O(log n)N/AO(log n)O(n)HashMapO(1)O(1)O(1)O(1)O(n)LinkedHashMapO(1)O(1)O(1)O(1)O(n)TreeMapO(log n)O(log n)O(log n)O(log n)O(n)
*:平均情况,最坏情况可能是O(n)
性能优化技巧:
为集合预分配容量,避免频繁扩容使用适当的初始容量和负载因子优先使用批量操作(如addAll)避免在for-each循环中删除元素在合适的场景使用并行流
8.3 常见陷阱
并发修改异常
在迭代过程中修改集合可能会导致ConcurrentModificationException:
List
list.add("Java");
list.add("Python");
list.add("C++");
// 错误方式:可能抛出ConcurrentModificationException
for (String language : list) {
if (language.equals("Python")) {
list.remove(language);
}
}
// 正确方式1:使用Iterator的remove方法
Iterator
while (iterator.hasNext()) {
String language = iterator.next();
if (language.equals("Python")) {
iterator.remove();
}
}
// 正确方式2:使用removeIf方法(Java 8+)
list.removeIf(language -> language.equals("Python"));
Arrays.asList()的固定大小
Arrays.asList()返回的列表是固定大小的,不能添加或删除元素:
String[] array = {"Java", "Python", "C++"};
List
try {
list.add("Go"); // 抛出UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("Cannot add to fixed-size list");
}
// 解决方案:转换为ArrayList
List
modifiableList.add("Go"); // 正常工作
Map.keySet()和values()返回的视图
Map.keySet()和values()返回的集合是Map的视图,修改这些集合会影响原始Map:
Map
map.put("Java", 1995);
map.put("Python", 1991);
Set
keys.remove("Java"); // 同时从map中移除Java键值对
System.out.println(map); // 输出: {Python=1991}
HashSet和HashMap的hashCode()和equals()合约
当使用自定义类作为HashSet的元素或HashMap的键时,必须正确实现hashCode()和equals()方法:
public class Person {
private String name;
private int age;
// 构造函数、getter和setter省略
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
线程安全问题
大多数集合类都不是线程安全的,在多线程环境中使用时需要同步:
// 方式1:使用Collections的同步包装器
List
Map
// 方式2:使用并发集合
List
Map
比较器的一致性
当使用自定义比较器时,确保符合一致性要求:
// 不一致的比较器(违反了传递性)
Comparator
@Override
public int compare(Integer a, Integer b) {
return (a % 2) - (b % 2); // 只比较奇偶性
}
};
9. 总结与进阶学习
9.1 总结
Java集合框架提供了一组丰富的接口和实现类,用于存储和操作数据:
核心接口:Collection, List, Set, Queue, Deque, Map主要实现:ArrayList, LinkedList, HashSet, LinkedHashSet, TreeSet, HashMap, LinkedHashMap, TreeMap特殊集合:并发集合(ConcurrentHashMap等)、不可变集合(Collections.unmodifiableXXX等)工具类:Collections, Arrays
选择合适的集合类型取决于多种因素,包括访问模式、排序要求、性能需求等。
9.2 进阶学习路径
要深入学习Java集合框架,可以考虑以下进阶主题:
Java 8+ Stream API:更加函数式的集合操作方式并发集合和原子操作:java.util.concurrent包中的类第三方集合库:如Apache Commons Collections, Google Guava集合性能优化:如何优化集合操作的性能数据结构原理:学习集合类背后的数据结构
9.3 学习资源
书籍:
“Java核心技术”(Horstmann & Cornell)“Effective Java”(Joshua Bloch)“Java编程思想”(Bruce Eckel)
在线资源:
Java官方文档:https://docs.oracle.com/en/java/javase/Java Tutorials: https://docs.oracle.com/javase/tutorial/collections/Baeldung关于Java集合的文章:https://www.baeldung.com/java-collections