J03 Java基础 03:集合框架

集合框架


Java 集合框架(Java Collections Framework, JCF) 是 Java 提供的一套标准化 API,用于处理集合(Collection)类的数据结构。

Java 集合框架包含以下几个核心部分:

  • 接口(Interfaces):定义了集合的基本行为规范,即上图黄色部分。
  • 实现类(Implementations):为接口提供具体实现,即上图蓝色和紫色部分。
  • 工具类(Utility Classes):提供对集合的操作支持,如排序、查找等。

在使用时,接口是不能直接实现的,需要使用具体的类实现。

collection接口


collection接口是所有集合类的跟接口,可以使用集合类的6种方式实现。

例如学生成绩储存的例子:

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
public class StudentScoreManager {
public static void main(String[] args) {
// 创建一个 Collection 对象,用于存储学生成绩
Collection<Double> scores = new ArrayList<>();
// 添加学生成绩
**scores.add(85.5);**
scores.add(90.0);
scores.add(78.5);
scores.add(88.0);
// 显示所有成绩
System.out.println("所有学生成绩:" + scores);
// 检查是否包含特定成绩
double targetScore = 90.0;
if (**scores.contains(targetScore)**) {
System.out.println("成绩 " + targetScore + " 存在于集合中。");
} else {
System.out.println("成绩 " + targetScore + " 不存在于集合中。");
}
// 删除某个成绩
**scores.remove(78.5);**
System.out.println("移除78.5后的成绩:" + scores);
// 计算总成绩和平均成绩
double total = 0.0;
for (**double score : scores**) {
total += score;
}
double average = total / **scores.size()**;
System.out.println("总成绩:" + total + ",平均成绩:" + average);
// 判断集合是否为空
if (**scores.isEmpty()**) {
System.out.println("集合为空。");
} else {
System.out.println("集合中有 " + scores.size() + " 个成绩。");
}
// 清空集合
**scores.clear()**;
System.out.println("清空后,集合:" + scores);
}
}
---
所有学生成绩:[85.5, 90.0, 78.5, 88.0]
成绩 90.0 存在于集合中。
移除78.5后的成绩:[85.5, 90.0, 88.0]
总成绩:263.5,平均成绩:87.83333333333333
集合中有 3 个成绩。
清空后,集合:[]

上面提供的例子中包括了Collection最为常用的几个api:

  • add ,contrains ,remove ,size ,isEmpty ,clear
  • 值得注意的是,在collection中, add ,contrains ,remove 都仅支持单一的参数Object o

以及用于比较两个集合元素是否相等的

equals


Collection同时还有迭代器Iterator接口,用于遍历集合中的元素。

Iterator 接口提供以下核心方法:

  • hasNext()
    • 检查是否还有元素可供迭代。
    • 返回值:true 表示有下一个元素,false 表示没有。
  • next()
    • 返回迭代的下一个元素。
    • 如果没有元素,则抛出 NoSuchElementException
  • remove()
    • 删除最近一次通过 next() 方法返回的元素。
    • 注意:此方法是可选操作,并且只能在调用 next() 之后调用一次。
1
2
3
4
5
6
7
8
9
// 使用 Iterator 遍历集合并移除低于 80 的成绩
Iterator<Double> iterator = scores.iterator();
while (iterator.hasNext()) {
double score = iterator.next();
if (score < 80) {
System.out.println("移除低于 80 的成绩:" + score);
iterator.remove();
}
}

List接口


List是继承自 Collection 接口的子接口,表示一个有序的元素集合,List 是专门为需要按照插入顺序存储和访问元素的场景设计的。按照插入顺序进行存储,允许元素重复,可以通过索引快速访问元素。List 的大小是动态的,不需要像数组那样提前定义长度。


对于list接口的api,可以从collection的不便之处的角度出发——不支持索引。

因此List接口的主要重写了collection用来增减集合元素的addremove 几个函数,同时增加了set , getindexOf这些用于控制元素组成的方法。在list版本中,这些方法接受的参数分别为:

  • add(int index,E e)
  • set(int index,E e)
  • get(int index)
  • remove(int index)
  • indexOf(E e)

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
public class ListOperationsDemo {
public static void main(String[] args) {
// **初始化List**
List<String> names = new ArrayList<>();
names.add("tomori");
names.add("soyo");
names.add("sakiko");
names.add("mutsumi");
names.add("taki");

System.out.println("初始列表: " + names);

// **依次移除元素**
names.remove(2); // 移除 "sakiko"
System.out.println("移除 'sakiko': " + names);

names.remove(2); // 移除 "mutsumi"
System.out.println("移除 'mutsumi': " + names);

names.remove(2); // 移除 "taki"
System.out.println("移除 'taki': " + names);

names.remove(1); // 移除 "soyo"
System.out.println("移除 'soyo': " + names);

// **添加新元素**
names.add("anon"); // 加入 "anon"
System.out.println("加入 'anon': " + names);

names.add("soyo"); // 再次加入 "soyo"
System.out.println("加入 'soyo': " + names);

names.add("taki"); // 再次加入 "taki"
System.out.println("加入 'taki': " + names);

// **设置特定位置的元素**
names.set(names.indexOf("taki"), "rikki"); // 将 "taki" 设置为 "rikki"
System.out.println("将 'taki' 设置为 'rikki': " + names);

names.add("laana"); // 加入 "laana"
System.out.println("加入 'laana': " + names);

names.set(names.indexOf("tomori"), "tomorin"); // 将 "tomori" 设置为 "tomorin"
System.out.println("将 'tomori' 设置为 'tomorin': " + names);

names.set(names.indexOf("soyo"), "soyorin"); // 将 "soyo" 设置为 "soyorin"
System.out.println("将 'soyo' 设置为 'soyorin': " + names);

// **最终列表**
System.out.println("最终列表: " + names);
}
}

LinkedList ArrayList Vector
底层实现 双向链表 动态数组 动态数组
线程安全
随机访问效率 低(O(n) 高(O(1) 高(O(1)
插入删除效率 高(O(1) 低(O(n) 低(O(n)
扩容方式 不适用 容量增加 1.5 倍 容量翻倍
使用场景 频繁插入和删除 随机访问频繁 线程安全的场景

Set 接口


Set接口是一个不允许重复元素的集合,不能包含两个相等的元素。元素的唯一性是基于equals()方法的定义。

根据不同的实现方式,Set可能会保留插入顺序,完全无序,或者按照某种方式排序。因此没有索引,不能通过索引访问。其操作与Collection很像:

  • boolean add(E e):向集合中添加元素,如果元素已存在,返回false
  • boolean remove(Object o):移除指定的元素,成功返回true
  • boolean contains(Object o):检查集合是否包含某个元素。
  • void clear():清空集合中的所有元素。
  • int size():返回集合中元素的数量。
  • boolean isEmpty():检查集合是否为空。

如上所述,Set接口的主要实现方式之间有一定的区别。

HashSet

  • 基于哈希表实现,使用HashMap作为底层数据结构。
  • 不保证元素的顺序。
  • 插入、删除、查找操作的时间复杂度为O(1)(在哈希函数表现良好的情况下)。

LinkedHashSet

  • 基于哈希表和链表实现,底层使用LinkedHashMapLinkedHashMapHashMap 的一个子类,它在存储键值对的同时,通过维护一个双向链表来记录元素的插入顺序。
  • 保留元素的插入顺序。
  • 时间复杂度略高于HashSet,但仍为O(1)

TreeSet

  • 基于红黑树实现,底层使用NavigableMap(通常是TreeMap)。
  • 保证元素的自然顺序(或通过提供的Comparator定义的顺序)。
  • 插入、删除、查找操作的时间复杂度为O(log n)

Map接口


Map 接口是集合框架中用于存储键值对(key-value)的数据结构,很像python中的字典。 与Set 相似,HashMap 不保证元素顺序,而 LinkedHashMap 保留插入顺序,TreeMap 保证键的自然顺序或自定义顺序。


由于MapCollection并不存在继承关系,他们的方法相差较大。大致上包括以下方法。

  • put(K key, V value):如果键已存在,返回旧值;否则返回 null
  • get(Object key):如果键不存在,返回 null
  • remove(Object key):如果键存在,返回旧值;否则返回 null
  • containsKey(Object key)
  • containsValue(Object value)
  • size()
  • isEmpty()
  • clear()
  • keySet():返回所有键的集合(Set)。
  • values():返回所有值的集合(Collection)。
  • entrySet():返回所有键值对的集合(Set<Map.Entry<K, V>>

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.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Collection;

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

// 使用 put 方法添加键值对
mygoBand.put("主唱", "高松灯");
mygoBand.put("吉他手", "千早爱音");
mygoBand.put("主音吉他手", "要乐奈");
mygoBand.put("贝斯手", "长崎爽世");
mygoBand.put("鼓手", "椎名立希");

// 使用 get 方法获取键对应的值
String vocalist = mygoBand.get("主唱");
System.out.println("主唱是: " + vocalist);

// 使用 containsKey 方法检查 Map 中是否包含某个键
boolean hasDrummer = mygoBand.containsKey("鼓手");
System.out.println("乐队中是否有鼓手? " + hasDrummer);

// 使用 containsValue 方法检查 Map 中是否包含某个值
boolean hasMember = mygoBand.containsValue("千早爱音");
System.out.println("乐队中是否有名为千早爱音的成员? " + hasMember);

// 使用 size 方法获取 Map 中的键值对数量
int bandSize = mygoBand.size();
System.out.println("乐队成员数量: " + bandSize);

// 使用 isEmpty 方法检查 Map 是否为空
boolean isBandEmpty = mygoBand.isEmpty();
System.out.println("乐队成员列表是否为空? " + isBandEmpty);

// 使用 keySet 方法获取所有键的集合
Set<String> roles = mygoBand.keySet();
System.out.println("乐队中的角色: " + roles);

// 使用 values 方法获取所有值的集合
Collection<String> members = mygoBand.values();
System.out.println("乐队成员的名字: " + members);

// 使用 entrySet 方法获取所有键值对的集合
Set<Map.Entry<String, String>> entries = mygoBand.entrySet();
System.out.println("乐队成员列表:");
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 使用 remove 方法删除指定键的键值对
String removedMember = mygoBand.remove("贝斯手");
System.out.println("被移除的成员: " + removedMember);

// 使用 clear 方法清空 Map 中的所有键值对
mygoBand.clear();
System.out.println("清空后的乐队成员列表: " + mygoBand);
}
}

Iterator迭代器和ListIterator迭代器


在collection部分简单提及了迭代器的使用。对于这些迭代器来说,它们的主要方法包括hasNext()next()remove()。因此,迭代器提供了一个遍历和修改集合中元素的方法。

但需要注意的是,在使用迭代器的同时不能使用如add 这些方法修改集合。


如果希望同时使用迭代器和修改元素,或者希望使用双向遍历等功能,可以使用ListIterator。这是专门为List设计的迭代器。

功能 Iterator ListIterator
单向遍历
双向遍历 ✅ (hasPrevious()previous()
移除元素 ✅(remove() ✅(remove()
添加元素 ✅(add(E e)
修改元素 ✅(set(E e)
索引支持 ✅(nextIndex()previousIndex()
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package collections;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class ListIteratorDemo {
public static void main(String[] args) {
// **初始化列表**
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");

System.out.println("初始列表: " + list);

// **演示Iterator不能同时迭代和add操作**
demonstrateIteratorLimitations(list);

// **演示ListIterator解决方案**
demonstrateListIteratorSolution(list);
}

private static void demonstrateIteratorLimitations(List<String> list) {
System.out.println("\n使用Iterator尝试迭代和添加元素:");

try {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("当前元素: " + element);
if (element.equals("B")) {
list.add("E"); // 尝试在迭代过程中添加元素
}
}
} catch (Exception e) {
System.out.println("发生异常: " + e.getClass().getSimpleName() + " - " + e.getMessage());
}

System.out.println("Iterator操作后的列表: " + list);
}

private static void demonstrateListIteratorSolution(List<String> list) {
System.out.println("\n使用ListIterator同时迭代和添加元素:");

ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
String element = listIterator.next();
System.out.println("当前元素: " + element);
if (element.equals("B")) {
listIterator.add("E"); // 使用ListIterator添加元素
System.out.println("添加元素 'E' 到列表");
}
}

System.out.println("ListIterator操作后的列表: " + list);
}
}
---
初始列表: [A, B, C, D]

使用Iterator尝试迭代和添加元素:
当前元素: A
当前元素: B
发生异常: ConcurrentModificationException - null
Iterator操作后的列表: [A, B, C, D, E]

使用ListIterator同时迭代和添加元素:
当前元素: A
当前元素: B
添加元素 'E' 到列表
当前元素: C
当前元素: D
当前元素: E
ListIterator操作后的列表: [A, B, E, C, D, E]

Iterator适用于简单的只读遍历,而ListIterator适用于需要在遍历过程中动态修改列表内容的场景。

参考


https://blog.csdn.net/grd_java/article/details/122403602

https://zh.wikipedia.org/zh-cn/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6

https://www.runoob.com/java/java-collections.html