1. Collection类

Collection 接口是 Java 集合框架中的根接口之一,定义了所有集合类共有的基本操作和行为。它位于 java.util 包中,继承自 Iterable 接口,因此所有实现了 Collection 的类都可以使用增强 for 循环进行遍历。


主要特点和方法

  • 基础操作

    • add(E e):向集合中添加一个元素。
    • remove(Object o):从集合中删除指定的元素。
    • clear():清空集合中的所有元素。
    • size():返回集合中元素的数量。
    • isEmpty():判断集合是否为空。
    • contains(Object o):判断集合中是否包含某个元素。
  • 遍历功能

    • 继承自 Iterableiterator() 方法,可以获取一个迭代器遍历集合中的元素。
    • 从 Java 8 开始,还可以通过 stream() 方法使用 Stream API 进行流式操作。
  • 批量操作

    • addAll(Collection<? extends E> c):将指定集合中的所有元素添加到当前集合中。
    • removeAll(Collection<?> c):删除当前集合中包含在指定集合中的所有元素。
    • retainAll(Collection<?> c):保留当前集合中与指定集合共有的元素。

注意事项

  • 抽象性质
    Collection 接口不能被直接实例化,而是由其子接口(如 ListSetQueue 等)来具体实现。

  • 无索引访问
    Collection 接口没有定义基于索引的访问方法(例如 get(int index)),如果需要索引访问,可以考虑使用 List 接口及其实现类(如 ArrayList)。

  • 扩展性
    所有 Java 集合类都遵循 Collection 接口定义的契约,这使得集合类之间可以进行通用的编程,而不用关注具体的实现细节。


2. List

  • get(int index) : 返回第index个元素
  • set(int index, Object obj) : 把第index个元素设置成obj
  • 有序性
    List 中的元素按照插入顺序排列,开发者可以通过索引(从 0 开始)来访问、插入或删除元素。

  • 允许重复
    Set 不同,List 允许集合中存在重复元素。

  • 扩展 Collection 接口
    除了 Collection 中的基本操作外,List 还提供了与索引有关的操作,如 get(int index)set(int index, E element)add(int index, E element)remove(int index)


主要方法

除了 Collection 提供的常用方法,List 接口还增加了以下与索引相关的操作:

  • 添加和插入

    • boolean add(E e):在列表末尾追加元素。
    • void add(int index, E element):在指定位置插入元素,其后的元素依次后移。
  • 访问元素

    • E get(int index):返回指定位置的元素。
    • E set(int index, E element):替换指定位置的元素,并返回原来的值。
  • 删除元素

    • E remove(int index):删除指定位置的元素,并返回被删除的元素。
    • boolean remove(Object o):删除第一次出现的指定对象(若存在)。
  • 查找元素

    • int indexOf(Object o):返回元素第一次出现的位置,如果不存在返回 -1。
    • int lastIndexOf(Object o):返回元素最后一次出现的位置。
  • 子列表操作

    • List<E> subList(int fromIndex, int toIndex):返回列表中指定范围的视图,视图中的修改会反映在原列表上。
  • 列表迭代器

    • ListIterator<E> listIterator():返回一个列表迭代器,可双向遍历列表,并在遍历过程中进行修改操作。

常见的实现类

Java 提供了多个 List 接口的实现,每种实现都有各自的特点和适用场景:

ArrayList

  • 底层实现:动态数组
  • 特点
    • 随机访问非常快(时间复杂度 O(1))
    • 插入和删除(尤其是中间位置)可能较慢,因为需要移动数组元素
    • 默认初始容量通常为 10,自动扩容时采用一定的增长策略
  • 适用场景:频繁读取,较少修改(尤其是尾部添加)的场景

LinkedList

  • 底层实现:双向链表
  • 特点
    • 插入和删除操作快(特别是在列表两端或已知节点处进行操作)
    • 随机访问较慢(需要遍历节点,时间复杂度 O(n))
    • 除了作为 List,它还实现了 Deque 接口,适合当作队列或双端队列使用
  • 适用场景:频繁在中间插入或删除元素的场景

Vector

  • 底层实现:动态数组
  • 特点
    • ArrayList 类似,但所有方法都是同步的,因此线程安全
    • 由于同步开销,性能相对较低
    • 作为较早期的集合类,现多推荐使用 ArrayList(如果线程安全需求,则可考虑 Collections.synchronizedListCopyOnWriteArrayList 等)
  • 适用场景:需要线程安全且对性能要求不太苛刻的场景

性能与使用注意

  • 随机访问 vs 插入删除

    • 如果你的应用需要频繁通过索引随机访问元素,建议使用 ArrayList
    • 如果需要在列表中间进行大量插入和删除操作,LinkedList 会更高效。
  • 线程安全问题

    • 默认情况下,ArrayListLinkedList 都不是线程安全的。如果需要线程安全的列表,可以使用 Collections.synchronizedList(new ArrayList<>()) 或选择并发集合如 CopyOnWriteArrayList(适合读多写少场景)。
  • 内存占用

    • ArrayList 在扩容时可能会分配较大的连续内存块,而 LinkedList 每个节点都需要额外的指针空间。

3. Set

3.1 HashSet

用哈希表实现, 和cpp un_orderset一样,无序, $O(1)$ 插入查询。

1
2
3
4
var se = new HashSet<E>();
se.add(??);

sout se.contain(???) ;

3.2 TreeSet

使用红黑树实现, 和cpp普通set一样, $O(logn)$ 插入查询
存储的元素必须实现 Comparable 接口,或者在构造时提供 Comparator。默认是按第一个元素的大小从小到大排序额,
Comparable 接口里面的compareTo(Object O) 用于排列, 和Struct 里面的bool operator > 一样

  • comparator() : 返回比较器,最普通的NULL
  • first() 最小的元素
  • last()
  • headSet()
  • tailSet()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import java.util.Comparator;
import java.util.TreeSet;

class Record {
String name;
int score;

public Record(String name, int score) {
this.name = name;
this.score = score;
}

@Override
public String toString() {
return name + ": " + score;
}
}

// 普通实现
public class TreeSetExample {
public static void main(String[] args) {
// 使用 TreeSet 并提供自定义比较器,按 score 降序排列
TreeSet<Record> recordSet = new TreeSet<>(new Comparator<Record>() {
@Override
public int compare(Record r1, Record r2) {
return Integer.compare(r2.score, r1.score); // 降序排序
}
});

// 添加数据
recordSet.add(new Record("Alice", 85));
recordSet.add(new Record("Bob", 92));
recordSet.add(new Record("Charlie", 78));
recordSet.add(new Record("David", 92)); // score 相同,Comparator 需要处理

// 遍历 TreeSet
for (Record r : recordSet) {
System.out.println(r);
}
}
}

//Lambda:

public class TreeSetLambdaExample {
public static void main(String[] args) {
// 使用 Lambda 表达式定义 Comparator
TreeSet<Record> recordSet = new TreeSet<>(
(r1, r2) -> r2.score != r1.score ? Integer.compare(r2.score, r1.score) : r1.name.compareTo(r2.name)
);

// 添加数据
recordSet.add(new Record("Alice", 85));
recordSet.add(new Record("Bob", 92));
recordSet.add(new Record("Charlie", 78));
recordSet.add(new Record("David", 92)); // 处理分数相同的情况

// 遍历 TreeSet
recordSet.forEach(System.out::println);
}
}


Map

Map没有继承Collection接口。

  • put(key, value)
  • containsKey(key)
  • containsValue(Object value)
  • get(Object key)
  • keySet(): 返回key值形成的集合
  • values() : 返回values形成的Collections集合
  • entrySet() : 返回一个Map.Entry类型的Set, 里面有getKey和getValue方法(Map.Entry)
  • merge(T Key, T Value, Operation) : Ope可以使用Lambda, 对已经有的Key进行合并操作

  • V getOrDefault(Object key, V defaultValue):获取键对应的值,如果不存在则返回默认值。
  • void forEach(BiConsumer<? super K, ? super V> action):遍历 Map 中所有键值对。
  • V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction):如果键不存在,则通过给定函数成一个值并插入。
  • V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction):如果键存在,则对其值进行重新计算。
  • V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction):合并键对应的值,可用于统计和聚合操作。

常见实现类

Java 提供了多种 Map 的实现,根据不同需求可以选择合适的实现类:

实现类 底层结构 排序/顺序 线程安全性 备注
HashMap 哈希表 无序 非线程安全 允许 null 键和 null 值
LinkedHashMap 哈希表 + 链表 维护插入顺序(或访问顺序) 非线程安全 相比 HashMap,多了顺序维护,性能略低
TreeMap 红黑树 按键的自然顺序或 Comparator 排序 非线程安全 键必须实现 Comparable 接口或提供 Comparator
Hashtable 哈希表 无序 线程安全(同步方法) 不允许 null 键和 null 值(较老的实现,不推荐使用)
ConcurrentHashMap 分段锁哈希表 无序 线程安全(高并发) 高效支持并发操作,适用于多线程环境

Java Map 与 C++ STL Map 的对比

特性 C++ STL Map Java Map
接口设计 容器类,通常直接作为类使用 接口(Map 接口)+具体实现类
排序/无序 std::map(红黑树实现,自动排序),std::unordered_map(哈希表实现) TreeMap(红黑树实现,排序),HashMap(哈希表实现,无序)
键允许 null 不支持 null 键(C++ 中 null 不适用) HashMap 允许一个 null 键;TreeMap 不允许 null 键
访问方式 使用 operator[]at() 方法 使用 get()put() 方法,不支持下标操作
迭代器 提供双向迭代器(对于有序 map) 提供 keySet()/entrySet() 等视图,通常用 foreach 遍历

使用示例

下面是一个简单的示例,展示如何使用 Map 接口及其常用方法(以 HashMap 为例),并演示如何遍历 Map 的不同视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.HashMap;
import java.util.Map;

public class MapExample {
public static void main(String[] args) {
// 创建一个 HashMap 实例
Map<String, Integer> scoreMap = new HashMap<>();

// 插入键值对
scoreMap.put("Alice", 85);
scoreMap.put("Bob", 92);
scoreMap.put("Charlie", 78);

// 获取值
int aliceScore = scoreMap.get("Alice");
System.out.println("Alice 的分数: " + aliceScore);

// 检查键是否存在
if (scoreMap.containsKey("David")) {
System.out.println("David 存在于 Map 中");
} else {
System.out.println("David 不存在于 Map 中");
}

// 使用 getOrDefault 方法
int davidScore = scoreMap.getOrDefault("David", 0);
System.out.println("David 的分数(默认值为 0): " + davidScore);

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

// 使用 Lambda 表达式遍历 Map
scoreMap.forEach((name, score) ->
System.out.println("Lambda 遍历: " + name + " -> " + score)
);

// 删除某个键值对
scoreMap.remove("Charlie");
System.out.println("删除 Charlie 后,Map 中的元素: " + scoreMap);
}
}

说明:

  • put():插入或更新键值对。
  • get() / getOrDefault():获取指定键的值。
  • containsKey():检查某个键是否存在。
  • remove():删除指定键的映射。
  • entrySet()、keySet()、values():提供对 Map 内部数据的视图,方便遍历。

6. 进阶:Map 的默认方法(Java 8+)

Java 8 引入了一系列默认方法,进一步简化了 Map 的操作。例如:

  • computeIfAbsent()
    如果指定的键不存在,则使用给定函数计算一个值并插入 Map:
1
scoreMap.computeIfAbsent("David", key -> 80);  // 如果 David 不存在,则赋值 80
  • merge()
    1
    `scoreMap.merge("Alice", 5, (oldVal, newVal) -> oldVal + newVal);  // Alice 的分数加 5`

这些默认方法使得对 Map 的操作更加灵活和简洁。