【Java】欸…?我学集合框架?真的假的?
Java集合框架
概述
Java集合框架主要由以下几个部分组成:
- 接口(Interfaces):定义了集合的基本操作,如添加、删除、遍历等。
- 实现(Implementations):提供了接口的具体实现,例如
ArrayList
、HashMap
等。 - 算法(Algorithms):如
Collections
和Arrays
类中定义的算法,用于操作集合元素。
为什么Java集合框架如此重要?
- 简化数据管理:集合框架提供了统一的方式来处理不同类型的数据集合。
- 提高性能:不同的集合类针对不同的使用场景进行了优化,可以提高程序的性能。
- 增强代码的可读性:使用集合框架可以使代码更加简洁和易于理解。
- 促进代码重用:集合框架的通用性使得编写的代码更容易被重用。
Collection接口
Collection接口概述
Collection
接口是Java集合框架中最基本的接口,它是所有单列集合的根接口。Collection
接口定义了适用于所有单列集合的操作,如添加、删除、遍历元素等。它提供了一系列通用的方法,使得对集合的操作变得统一和方便。
创建方式:
Collection<E> 对象名 = new 实现类对象<E>()
Collection接口的主要方法
以下是Collection
接口中定义的一些核心方法:
boolean add(E e)
: 向集合中添加一个元素。boolean remove(Object o)
: 从集合中移除一个指定的元素。boolean contains(Object o)
: 检查集合是否包含指定的元素。int size()
: 返回集合中的元素数量。boolean isEmpty()
: 判断集合是否为空。boolean containsAll(Collection<?> c)
: 检查集合是否包含另一个集合的所有元素。boolean addAll(Collection<? extends E> c)
: 将指定集合的所有元素添加到当前集合中。boolean removeAll(Collection<?> c)
: 从当前集合中移除指定集合中的所有元素。boolean retainAll(Collection<?> c)
: 仅保留当前集合和指定集合共有的元素。void clear()
: 清空集合中的所有元素。Iterator<E> iterator()
: 返回一个迭代器,用于遍历集合中的元素。
迭代器
迭代器(Iterator)
迭代器是一种设计模式,用于顺序访问集合中的元素。在Java集合框架中,迭代器提供了一种统一的方法来遍历集合中的元素,而不需要了解集合的具体实现细节。Iterator
接口定义在java.util
包中,并且是java.lang.Iterable
接口的一部分。
Iterator接口的主要方法
boolean hasNext()
: 返回是否还有下一个元素可以迭代。E next()
: 返回迭代的下一个元素。void remove()
: 从集合中移除当前迭代的元素。
迭代器的使用
迭代器的使用通常如下:
复制Collection<E> collection = ...; // 某个集合实例
Iterator<E> iterator = collection.iterator();
while (iterator.hasNext()) {
E element = iterator.next();
// 处理元素
}
并发修改异常(ConcurrentModificationException)
在Java集合框架中,当一个集合在迭代过程中被修改(不是通过迭代器自身的remove
方法),就会抛出ConcurrentModificationException
异常。这个异常的目的是防止在迭代过程中集合结构被外部修改,这可能会导致不可预测的行为或违反迭代器的期望行为。
并发修改异常的常见场景
- 在迭代过程中直接调用集合的
add
、remove
等修改方法。 - 使用并发线程修改集合,而没有采取适当的同步措施。
避免并发修改异常的方法
- 使用迭代器的
remove
方法来删除元素,该方法会告知迭代器集合已经被修改,从而避免异常。 - 使用
CopyOnWriteArrayList
这样的并发集合,它们允许在迭代过程中进行修改,而不会引发异常。 - 在多线程环境下,使用适当的同步机制,如
synchronized
块或ConcurrentHashMap
等并发集合。
List接口
List接口
List
接口是Collection
接口的一个子接口,它是一个有序的集合,可以包含重复的元素。List
接口提供了一些额外的方法,用于操作元素的顺序和插入点。
List接口的主要方法
void add(int index, E element)
: 在指定位置插入一个元素。E get(int index)
: 返回指定位置的元素。E set(int index, E element)
: 替换指定位置的元素。E remove(int index)
: 移除指定位置的元素并返回被移除的元素。int indexOf(Object o)
: 返回指定元素在列表中的第一次出现的位置。int lastIndexOf(Object o)
: 返回指定元素在列表中的最后一次出现的位置。ListIterator<E> listIterator()
: 返回一个ListIterator
(列表迭代器),允许对列表进行更复杂的操作。
ArrayList
ArrayList
是基于数组实现的List
接口的实现类。它允许对元素进行快速随机访问。
ArrayList的特点
- 动态数组:
ArrayList
内部使用一个数组来存储元素,可以根据需要动态调整大小。 - 快速随机访问:通过索引访问元素非常快速。
- 不是线程安全的:
ArrayList
不是线程安全的,多线程环境下需要外部同步。
ArrayList的适用场景
- 当需要快速访问列表中的元素时。
- 当列表的大小变化不是非常频繁时。
ArrayList的常用方法
add(E e)
: 在列表末尾添加一个元素。get(int index)
: 通过索引获取元素。remove(int index)
: 移除指定索引处的元素。
ArrayList的使用示例
List<String> arrayList = new ArrayList<>();
arrayList.add("Java");
arrayList.add("Python");
arrayList.add("C++");
String language = arrayList.get(1); // 获取索引为1的元素 "Python"
arrayList.remove(2); // 移除索引为2的元素 "C++"
LinkedList
LinkedList
是基于链表实现的List
接口的实现类,同时也是Queue
接口的一个实现。
LinkedList的特点
- 双向链表:
LinkedList
内部使用双向链表来存储元素。 - 插入和删除操作高效:在列表的头部、尾部或指定位置插入和删除元素非常高效。
- 随机访问慢:由于链表的特性,随机访问元素较慢。
LinkedList的适用场景
- 当需要频繁插入和删除元素时。
- 当需要实现栈、队列或双端队列时。
LinkedList的常用方法
add(E e)
: 在列表末尾添加一个元素。add(int index, E element)
: 在指定位置插入一个元素。remove(int index)
: 移除指定位置的元素。getFirst()
: 获取列表的第一个元素。getLast()
: 获取列表的最后一个元素。
LinkedList的使用示例
List<String> linkedList = new LinkedList<>();
linkedList.add("Java");
linkedList.add("Python");
linkedList.addFirst("C"); // 在列表头部添加元素
String firstLanguage = linkedList.getFirst(); // 获取第一个元素 "C"
linkedList.removeLast(); // 移除最后一个元素 "Python"
Vector
Vector
是一个古老的List
实现,与ArrayList
类似,但它是同步的。
Vector的特点
- 同步的动态数组:
Vector
内部使用一个数组来存储元素,并且是线程安全的。 - 性能较低:由于其线程安全性,
Vector
的性能通常低于ArrayList
。 - 遗留类:
Vector
是Java早期版本的一部分,现在已经不推荐使用。
Vector的适用场景
- 在非常老的代码库中可能会遇到
Vector
,但在新的代码中应该避免使用。
Vector的常用方法
与ArrayList
类似,Vector
提供了相同的方法,但由于其线程安全性,它还有一些额外的方法,如synchronized
版本的迭代器。
Vector的使用示例
复制List<String> vector = new Vector<>();
vector.addElement("Java");
vector.addElement("Python");
vector.addElement("C++");
String language = vector.elementAt(1); // 获取索引为1的元素 "Python"
vector.removeElementAt(2); // 移除索引为2的元素 "C++"
Set接口
Set接口
Set
接口是Java集合框架中Collection
接口的一个子接口,它是一个不允许包含重复元素的集合。Set
接口没有继承自List
接口,因此它不保证元素的顺序,也不支持索引访问。
Set接口的主要方法
boolean add(E e)
: 添加一个元素,如果元素已存在,则返回false
。boolean remove(Object o)
: 移除指定的元素。boolean contains(Object o)
: 检查集合是否包含指定的元素。Iterator<E> iterator()
: 返回一个迭代器,用于遍历集合中的元素。
HashSet
HashSet
是基于哈希表的Set
实现,它不保证元素的顺序,并且允许空(null)元素。
HashSet的特点
- 基于哈希表:
HashSet
的实现依赖于HashMap
,因此它提供了快速的查找速度。 - 无序:元素没有特定的顺序。
- 允许单个null元素:
HashSet
可以包含一个null元素。
HashSet的适用场景
- 当需要存储不重复的元素集合时。
- 当元素的顺序不重要时。
HashSet的常用方法
add(E e)
: 添加元素。remove(Object o)
: 移除元素。contains(Object o)
: 检查集合是否包含元素。
HashSet的使用示例
Set<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Cherry");
boolean containsBanana = hashSet.contains("Banana"); // 返回true
boolean removed = hashSet.remove("Apple"); // 返回true
TreeSet
TreeSet
是基于红黑树的Set
实现,它可以确保元素处于排序状态。
TreeSet的特点
- 有序:所有的元素都会按照自然顺序或构造时提供的比较器进行排序。
- 不允许null元素:与
HashSet
不同,TreeSet
不允许包含null元素。
TreeSet的适用场景
- 当需要有序的元素集合时。
- 当需要维护元素的排序状态时。
TreeSet的常用方法
add(E e)
: 添加元素到排序后的集合中。first()
: 返回第一个(最小)元素。last()
: 返回最后一个(最大)元素。
TreeSet的使用示例
Set<String> treeSet = new TreeSet<>();
treeSet.add("Apple");
treeSet.add("Banana");
treeSet.add("Cherry");
String firstFruit = treeSet.first(); // 返回"Apple"
String lastFruit = treeSet.last(); // 返回"Cherry"
LinkedHashSet
LinkedHashSet
类似于HashSet
,但它维护了元素的插入顺序。
LinkedHashSet的特点
- 有序:元素按照插入顺序进行排序。
- 性能:通常比
HashSet
慢,因为需要维护插入顺序。
LinkedHashSet的适用场景
- 当需要存储不重复的元素集合,并且需要保持元素的插入顺序时。
LinkedHashSet的常用方法
与HashSet
相同。
LinkedHashSet的使用示例
复制Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Apple");
linkedHashSet.add("Banana");
linkedHashSet.add("Cherry");
// 遍历将按照添加的顺序:Apple, Banana, Cherry
for (String fruit : linkedHashSet) {
System.out.println(fruit);
}
Map接口
Map接口
Map
接口是Java集合框架的一部分,它存储的是键值对(key-value pairs),其中每个键(key)映射到一个值(value)。Map
接口不保证元素的顺序,并且不允许键重复。
Map接口的主要方法
V put(K key, V value)
: 将指定的值与此映射中的指定键关联。V get(Object key)
: 返回指定键所映射的值。V remove(Object key)
: 移除指定键的映射关系并返回其值。int size()
: 返回映射中键值对的数量。boolean isEmpty()
: 判断映射是否为空。Set<K> keySet()
: 返回映射中包含的键的Set视图。Collection<V> values()
: 返回映射中包含的值的Collection视图。Set<Map.Entry<K, V>> entrySet()
: 返回映射中包含的键值对的Set视图。
HashMap
HashMap
是基于哈希表的Map
实现,它允许空键和空值。
HashMap的特点
- 基于哈希表:
HashMap
使用键对象的hashCode()
值来存储和检索键值对。 - 无序:
HashMap
不保证映射的顺序。 - 允许空键和空值:可以有一个或多个空键和空值。
HashMap的适用场景
- 当需要快速查找键值对时。
- 当元素的顺序不重要时。
HashMap的常用方法
put(K key, V value)
: 添加或更新键值对。get(Object key)
: 根据键获取值。remove(Object key)
: 根据键移除键值对。keySet()
: 获取所有键的集合。values()
: 获取所有值的集合。entrySet()
: 获取所有键值对的集合。
HashMap的使用示例
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Apple", 1);
hashMap.put("Banana", 2);
Integer appleCount = hashMap.get("Apple"); // 获取键"Apple"的值,返回1
hashMap.remove("Banana"); // 移除键"Banana"的键值对
TreeMap
TreeMap
是基于红黑树的Map
实现,它可以确保键的排序。
TreeMap的特点
- 有序:所有的键都会根据其自然顺序或构造时提供的比较器进行排序。
- 不允许空键:
TreeMap
不允许空键,但可以有多个空值。
TreeMap的适用场景
- 当需要有序的键值对映射时。
- 当需要维护键的排序状态时。
TreeMap的常用方法
put(K key, V value)
: 添加或更新键值对。firstKey()
: 返回第一个(最小)键。lastKey()
: 返回最后一个(最大)键。descendingKeySet()
: 返回按降序排列的键集合。
TreeMap的使用示例
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Apple", 1);
treeMap.put("Banana", 2);
String firstFruit = treeMap.firstKey(); // 获取第一个键,返回"Apple"
String lastFruit = treeMap.lastKey(); // 获取最后一个键,返回"Banana"
LinkedHashMap
LinkedHashMap
类似于HashMap
,但它维护了插入顺序或者访问顺序。
LinkedHashMap的特点
- 有序:元素按照插入顺序或访问顺序进行排序。
- 允许空键和空值:可以有一个或多个空键和空值。
LinkedHashMap的适用场景
- 当需要保持键值对的插入顺序或访问顺序时。
LinkedHashMap的常用方法
与HashMap
相同。
LinkedHashMap的使用示例
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("Apple", 1);
linkedHashMap.put("Banana", 2);
// 遍历将按照添加的顺序:Apple, Banana
for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + " => " + entry.getValue());
}
结语
集合框架接口之间继承实现关系复杂,实现类繁多,要想运用自如还需多加努力,尽量对每一个常见的实现类都作充分了解,此后笔者还会继续学习泛型(真假泛型、泛型擦除)相关的知识。
参考文献:
Java集合框架最全详解(看这篇就够了)_java 集合框架-CSDN博客
Java集合框架最全详解(超详细)保姆级-CSDN博客
Java 备忘清单 & java cheatsheet & Quick Reference