Java集合框架(Java Collections Framework)是Java语言中用于存储和操作数据集合的一组接口和类。这个框架从JDK 1.2开始引入,经过多年发展已经成为Java开发中最基础也是最重要的组成部分之一。集合框架的核心设计理念是提供一组标准化的接口和实现,让开发者能够以统一的方式处理不同类型的数据集合。
在实际开发中,我们几乎每天都会与集合框架打交道。无论是存储用户列表、缓存数据对象,还是进行各种数据操作,集合类都是不可或缺的工具。理解集合框架的基本接口,是掌握Java编程的重要基础。
Collection是集合框架中最顶层的接口,位于java.util包中。它定义了所有集合类共有的基本操作:
java复制public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
// JDK 8新增的默认方法
default boolean removeIf(Predicate<? super E> filter) {...}
// 其他方法...
}
Collection接口有几个重要特点:
注意:Collection接口本身不能被实例化,它只是定义了集合的基本行为规范。实际使用时需要选择具体的实现类,如ArrayList、HashSet等。
List接口继承自Collection,表示有序的集合(也称为序列)。与普通集合不同,List中的元素是有序的,并且可以通过索引(位置)精确控制每个元素的插入位置。
java复制public interface List<E> extends Collection<E> {
// 继承自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);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);
}
List接口的关键特性包括:
常见的List实现类有ArrayList、LinkedList和Vector。ArrayList基于动态数组实现,随机访问效率高;LinkedList基于双向链表实现,插入删除效率高;Vector是线程安全的版本,但性能较差。
Set接口同样继承自Collection,表示不允许重复元素的集合。Set最重要的特性就是元素的唯一性,它不保证元素的顺序(除非使用特定的实现类如LinkedHashSet)。
java复制public interface Set<E> extends Collection<E> {
// 继承自Collection的方法...
// Set接口没有定义新的方法,但对其中的方法有额外的约定
}
Set接口的关键特性:
常见的Set实现类有HashSet、LinkedHashSet和TreeSet。HashSet基于哈希表实现,提供最快的查找性能;LinkedHashSet在HashSet基础上维护了插入顺序;TreeSet基于红黑树实现,元素按自然顺序或自定义比较器排序。
Queue接口继承自Collection,表示一种特殊的集合,设计用于在处理前保存元素。Queue通常(但不一定)以FIFO(先进先出)的方式排序元素。
java复制public interface Queue<E> extends Collection<E> {
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
}
Queue接口定义了两种形式的操作:
常见的Queue实现类有LinkedList(也实现了List接口)、PriorityQueue和ArrayDeque。PriorityQueue是基于优先级堆的无界队列,元素按自然顺序或比较器排序;ArrayDeque是基于数组的双端队列实现。
Deque(双端队列)接口继承自Queue,支持在两端插入和移除元素。它既可以作为FIFO队列使用,也可以作为LIFO(后进先出)栈使用。
java复制public interface Deque<E> extends Queue<E> {
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
// 其他方法...
}
Deque接口的关键特性:
ArrayDeque是Deque的高效实现,通常优于LinkedList。当需要栈功能时,应优先使用Deque而不是遗留的Stack类。
虽然Map接口不属于Collection接口的继承体系(它独立存在),但它是集合框架中极其重要的一部分。Map表示键值对的集合,不允许重复的键。
java复制public interface Map<K,V> {
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void putAll(Map<? extends K, ? extends V> m);
void clear();
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K,V>> entrySet();
// 其他方法...
}
Map接口的关键特性:
常见的Map实现类有HashMap、LinkedHashMap、TreeMap和Hashtable。HashMap基于哈希表实现,提供最快的查找性能;LinkedHashMap维护了插入顺序;TreeMap基于红黑树实现,按键的自然顺序或比较器排序;Hashtable是线程安全的遗留类,通常应该用ConcurrentHashMap替代。
理解集合框架中接口的继承关系对于正确使用它们非常重要。下面是主要的继承关系图:
code复制Iterable
└── Collection
├── List
├── Set
│ ├── SortedSet
│ └── NavigableSet (JDK 6+)
└── Queue
└── Deque
独立接口:
Map
├── SortedMap
└── NavigableMap (JDK 6+)
从JDK 1.5开始,集合框架全面支持泛型,所有接口和类都是泛型化的。这意味着我们可以在编译时检查集合中元素的类型,避免运行时类型转换错误。
在实际开发中,如何选择合适的集合接口?这里有一些经验法则:
需要有序且允许重复元素:选择List接口,具体实现:
需要唯一元素:选择Set接口,具体实现:
需要队列功能:
需要键值对存储:选择Map接口,具体实现:
提示:从JDK 7开始,对于小型集合,可以使用Collections类的静态工厂方法(如Collections.emptyList()、Collections.singletonList()等)来创建不可变的集合实例,这在某些场景下可以提高性能。
当使用HashSet、HashMap等基于哈希的集合时,正确实现元素的equals()和hashCode()方法至关重要。这两个方法必须遵守以下约定:
不遵守这些约定会导致集合行为异常,例如在HashSet中可能出现"丢失"元素的情况。
在使用迭代器遍历集合时,如果直接通过集合的方法(而非迭代器的方法)修改集合,会抛出ConcurrentModificationException:
java复制List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
if ("b".equals(s)) {
list.remove(s); // 抛出ConcurrentModificationException
}
}
正确的做法是使用迭代器的remove()方法:
java复制Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("b".equals(s)) {
it.remove(); // 正确的方式
}
}
对于基于哈希的集合(HashSet、HashMap等),设置合理的初始容量和负载因子可以显著提高性能:
默认负载因子(0.75)在时间和空间成本之间提供了良好的折衷。较高的值减少了空间开销但增加了查找成本。设置初始容量时,应考虑预期元素数量及其负载因子,以尽量减少rehash操作次数。
从JDK 9开始,可以使用List.of()、Set.of()、Map.of()等工厂方法创建不可变集合:
java复制List<String> immutableList = List.of("a", "b", "c");
Set<Integer> immutableSet = Set.of(1, 2, 3);
Map<String, Integer> immutableMap = Map.of("a", 1, "b", 2);
这些集合具有以下特点:
集合与数组之间的转换是常见操作:
java复制// 集合转数组
List<String> list = Arrays.asList("a", "b", "c");
String[] array1 = list.toArray(new String[0]); // 推荐方式
String[] array2 = list.toArray(new String[list.size()]);
// 数组转集合
List<String> list1 = Arrays.asList(array1); // 固定大小列表
List<String> list2 = new ArrayList<>(Arrays.asList(array1)); // 可变列表
注意:Arrays.asList()返回的列表是固定大小的,任何试图改变列表大小的操作都会抛出UnsupportedOperationException。如果需要可变列表,应该使用new ArrayList<>(Arrays.asList(...))。
Java 8为集合框架引入了许多强大的新特性:
Stream API允许以声明式方式处理集合:
java复制List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
long count = names.stream()
.filter(name -> name.length() > 4)
.count();
Stream的主要特点:
Collection接口新增了多个默认方法:
java复制default boolean removeIf(Predicate<? super E> filter) {...}
default Spliterator<E> spliterator() {...}
default Stream<E> stream() {...}
default Stream<E> parallelStream() {...}
这些方法为所有集合实现提供了统一的功能,而不需要修改具体的实现类。
Map接口新增了许多实用方法:
java复制default V getOrDefault(Object key, V defaultValue) {...}
default void forEach(BiConsumer<? super K, ? super V> action) {...}
default V putIfAbsent(K key, V value) {...}
default boolean remove(Object key, Object value) {...}
default V replace(K key, V value) {...}
default boolean replace(K key, V oldValue, V newValue) {...}
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {...}
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {...}
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {...}
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {...}
default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {...}
这些方法大大简化了常见的Map操作模式。
不同集合实现类的性能特征差异很大:
ArrayList vs LinkedList:
HashSet vs TreeSet:
HashMap vs TreeMap:
对于基本数据类型,使用专门的集合类可以避免自动装箱/拆箱的开销:
java复制// 使用普通集合(有装箱开销)
List<Integer> list = new ArrayList<>();
list.add(1); // 自动装箱
// 使用专门集合(无装箱开销)
IntList intList = new IntArrayList(); // 来自第三方库如Eclipse Collections
intList.add(1); // 无装箱
Java标准库从JDK 8开始也提供了类似的优化,如Stream的原始类型特化(IntStream、LongStream等)。
在多线程环境下,应优先使用并发集合而非同步包装器:
java复制// 不推荐(性能较差)
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
// 推荐(更好的并发性能)
Map<String, String> map = new ConcurrentHashMap<>();
Java并发包(java.util.concurrent)提供了多种线程安全的集合实现:
对于大量小型集合,内存效率变得重要:
Collection接口本身不继承这些接口,但大多数具体实现类都实现了它们。这样设计的原因是:
主要区别:
Map和Collection代表不同的抽象:
不过,Map提供了三种集合视图(keySet()/values()/entrySet())来与Collection框架交互。
考虑因素:
实现一个高效且线程安全的TreeMap非常复杂。Java提供了ConcurrentSkipListMap作为替代,它基于跳表实现,提供类似的排序功能但并发性能更好。