JAVA之集合

集合概述

集合(Collection)在计算机科学中指的是一组元素的整体,用于处理一组相关的数据。Java 的集合框架提供了多种类型的集合类和接口,以便于存储和操作数据。 ### 1. 集合的定义

集合是由若干个确定的元素构成的整体。例如,数学中的集合包括有限集合(如班级所有同学)、无限集合(如自然数集合)等。

2. Java 集合框架

Java 的 java.util 包提供了丰富的集合类和接口,包括:

  • Collection 接口:所有集合类的根接口,不包括 Map
  • List 接口:有序集合,允许重复元素。常用实现类:
    • ArrayList:动态数组实现,支持快速随机访问。
    • LinkedList:链表实现,支持高效的插入和删除操作。
  • Set 接口:无序集合,不允许重复元素。常用实现类:
    • HashSet:基于哈希表的实现,不保证顺序。
    • LinkedHashSet:基于链表和哈希表,维护插入顺序。
    • TreeSet:基于红黑树实现,按自然顺序或自定义顺序排序。
  • Map 接口:键值对映射,不是 Collection 的子接口。常用实现类:
    • HashMap:基于哈希表,允许 null 键和 null 值。
    • LinkedHashMap:维护插入顺序的哈希表实现。
    • TreeMap:基于红黑树实现,按键的自然顺序或自定义顺序排序。

3. 集合的特性

  • 动态大小:大多数集合类(如 ArrayList)可以动态调整大小,而数组的大小一旦初始化后不可变。
  • 迭代器:集合元素的访问通过迭代器(Iterator)实现,提供统一的遍历方式,不依赖于具体的存储实现。

4. 遗留类和接口

  • 遗留类
    • Hashtable:线程安全的 Map 实现,已被 HashMap 取代。
    • Vector:线程安全的 List 实现,已被 ArrayList 取代。
    • Stack:基于 Vector 的栈实现,现代开发中通常使用 Deque 作为栈。
  • 遗留接口
    • Enumeration:已被 Iterator 取代,提供集合的遍历功能。

5. 泛型支持

Java 集合类支持泛型,使得集合只能存储指定类型的元素,提供类型安全性。例如,List<String> list = new ArrayList<>(); 只能存储 String 类型的元素。

List

Java List 接口详解

List 是 Java 集合框架中的一个接口,代表有序集合,允许重复元素,并通过索引访问元素。List 的常见实现包括 ArrayListLinkedList,每种实现都有其特定的用途和性能特点。

1. ArrayList 示例

`ArrayList 是 Java 集合框架中的一个重要类,提供了动态数组的实现方式,适合需要频繁访问元素的场景。下面是一些 ArrayList 的特点和使用方法的详细介绍:

ArrayList 的特点

  1. 动态大小: ArrayList 的容量会自动增长,以适应添加的元素。这是通过内部数组的扩展来实现的。

  2. 快速随机访问: 支持通过索引直接访问元素,时间复杂度为 (O(1))。这使得 ArrayList 非常适合需要快速访问的场景。

  3. 插入和删除: 在列表中间插入和删除元素的操作较慢,因为这些操作可能涉及到移动其他元素以保持顺序。插入或删除操作的时间复杂度为 (O(n)),其中 (n) 是元素的数量。

  4. 线程不安全: ArrayList 不是线程安全的。如果多个线程同时访问一个 ArrayList,并且至少有一个线程修改了列表结构,则必须在外部进行同步处理。

  5. 允许 null 元素: ArrayList 可以存储 null 元素。

详细示例

创建和操作 ArrayList

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.ArrayList;
import java.util.List;

public class ArrayListExample {
public static void main(String[] args) {
// 创建 ArrayList 实例
List<String> arrayList = new ArrayList<>();

// 添加元素
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");
System.out.println("ArrayList: " + arrayList);

// 访问元素
String fruit = arrayList.get(1); // 获取索引1处的元素
System.out.println("Element at index 1: " + fruit);

// 插入元素
arrayList.add(1, "Blueberry"); // 在索引1处插入元素
System.out.println("ArrayList after insertion: " + arrayList);

// 删除元素
arrayList.remove("Banana"); // 移除指定元素
System.out.println("ArrayList after removal: " + arrayList);

// 子列表
List<String> subList = arrayList.subList(1, 3); // 获取索引1到2的子列表
System.out.println("SubList: " + subList);

// 遍历 ArrayList
for (String item : arrayList) {
System.out.println("Item: " + item);
}

// 其他操作
int size = arrayList.size(); // 获取 ArrayList 的大小
System.out.println("Size of ArrayList: " + size);

// 清空 ArrayList
arrayList.clear();
System.out.println("ArrayList after clear: " + arrayList);
}
}

ArrayList 方法简介

  • add(E e): 在列表的末尾添加元素 e
  • add(int index, E element): 在指定位置 index 插入元素 element
  • get(int index): 返回指定位置 index 的元素。
  • remove(Object o): 移除首次出现的元素 o
  • remove(int index): 移除指定位置 index 的元素。
  • subList(int fromIndex, int toIndex): 返回列表中从 fromIndextoIndex(不包括 toIndex)的子列表。
  • size(): 返回列表中元素的数量。
  • clear(): 移除列表中的所有元素。

性能考虑

虽然 ArrayList 提供了快速的随机访问,但在以下场景中,LinkedList 可能是更好的选择:

  • 频繁的插入和删除操作: 如果需要在列表中间频繁插入或删除元素,LinkedList 的性能通常比 ArrayList 更好,因为 LinkedList 不需要移动其他元素。

ArrayList 是一个灵活且高效的实现,用于存储和访问动态数组中的元素。了解它的特点和使用方法有助于在开发中选择合适的数据结构,以实现最佳性能。

2. LinkedList 示例

LinkedList 是 Java 集合框架中的另一个重要类,基于双向链表实现,适用于频繁进行插入和删除操作的场景。下面是 LinkedList 的详细特点和使用示例:

LinkedList 的特点

  1. 插入和删除操作高效:
    • 在链表的任意位置插入或删除元素时,只需要调整节点的引用。因此,插入和删除操作的时间复杂度为 (O(1)),当在已知位置进行时。这使得 LinkedList 在这些操作中比 ArrayList 更高效,尤其是当操作发生在链表的中间部分时。
  2. 较慢的随机访问:
    • 由于 LinkedList 是基于链表结构的,访问特定索引处的元素需要从链表的头部开始遍历,因此随机访问操作的时间复杂度为 (O(n)),其中 (n) 是链表的长度。这使得在需要频繁访问元素的场景中,LinkedList 不如 ArrayList 高效。
  3. 双向链表:
    • LinkedList 使用双向链表实现,这意味着每个节点都持有对前一个和后一个节点的引用。这使得在链表的两端进行操作更加高效,比如 addFirstaddLast 操作都能在常数时间内完成。
  4. 线程不安全:
    • LinkedList 不是线程安全的。如果需要在多线程环境下使用 LinkedList,则需要在外部进行同步处理。
  5. 支持队列和双端队列操作:
    • LinkedList 实现了 QueueDeque 接口,因此可以用作队列或双端队列,支持更丰富的操作,比如 offerpollpeek 等方法。

详细示例

创建和操作 LinkedList

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
import java.util.LinkedList;
import java.util.List;

public class LinkedListExample {
public static void main(String[] args) {
// 创建 LinkedList 实例
List<String> linkedList = new LinkedList<>();

// 添加元素
linkedList.add("Dog");
linkedList.add("Elephant");
linkedList.add("Fox");
System.out.println("LinkedList: " + linkedList);

// 访问元素
String animal = linkedList.get(2); // 获取索引2处的元素
System.out.println("Element at index 2: " + animal);

// 插入元素
linkedList.add(1, "Cat"); // 在索引1处插入元素
System.out.println("LinkedList after insertion: " + linkedList);

// 删除元素
linkedList.remove("Elephant"); // 移除指定元素
System.out.println("LinkedList after removal: " + linkedList);

// 作为队列使用
linkedList.offer("Giraffe"); // 将元素添加到队列尾部
System.out.println("LinkedList after offer: " + linkedList);

String head = linkedList.poll(); // 移除并获取队列头部元素
System.out.println("Head of the queue: " + head);
System.out.println("LinkedList after poll: " + linkedList);

// 作为双端队列使用
linkedList.addFirst("Lion"); // 将元素添加到双端队列头部
linkedList.addLast("Tiger"); // 将元素添加到双端队列尾部
System.out.println("LinkedList after adding to both ends: " + linkedList);

String firstElement = linkedList.getFirst(); // 获取双端队列头部元素
String lastElement = linkedList.getLast(); // 获取双端队列尾部元素
System.out.println("First element: " + firstElement);
System.out.println("Last element: " + lastElement);

linkedList.removeFirst(); // 移除双端队列头部元素
linkedList.removeLast(); // 移除双端队列尾部元素
System.out.println("LinkedList after removing from both ends: " + linkedList);
}
}

LinkedList 方法简介

  • add(E e): 在列表的末尾添加元素 e
  • add(int index, E element): 在指定位置 index 插入元素 element
  • get(int index): 返回指定位置 index 的元素。
  • remove(Object o): 移除首次出现的元素 o
  • remove(int index): 移除指定位置 index 的元素。
  • addFirst(E e): 在双端队列的头部添加元素 e
  • addLast(E e): 在双端队列的尾部添加元素 e
  • getFirst(): 返回双端队列头部的元素。
  • getLast(): 返回双端队列尾部的元素。
  • offer(E e): 将元素 e 添加到队列尾部(适用于队列操作)。
  • poll(): 移除并返回队列头部的元素(适用于队列操作)。

性能考虑

  • 插入和删除: LinkedList 在插入和删除操作中的性能较好,尤其是在中间位置,因为这些操作只涉及到节点引用的调整,而不需要移动其他元素。
  • 访问: 如果频繁需要访问元素,尤其是随机访问,ArrayList 更适合,因为它的访问速度较快。

LinkedList 提供了一种高效的链式数据结构,特别适合需要频繁插入和删除的场景。了解其特点和适用场景有助于在编程中选择最适合的数据结构以优化性能。

3. 使用 List 的接口方法

使用 List 接口的方法可以帮助我们高效地操作列表中的数据。下面是 List 接口常用方法的详细说明和示例:

List 接口常用方法

1. add(E e)

  • 说明: 将元素 e 添加到列表的末尾。
  • 时间复杂度: 平均 (O(1))。
  • 示例:
    1
    list.add("Java"); // 添加 "Java" 到列表末尾

2. get(int index)

  • 说明: 返回指定位置 index 的元素。
  • 时间复杂度: (O(1))(随机访问)。
  • 示例:
    1
    String element = list.get(1); // 获取索引为 1 的元素

3. set(int index, E element)

  • 说明: 用指定元素 element 替换列表中指定位置 index 的元素。
  • 时间复杂度: (O(1))(随机访问)。
  • 示例:
    1
    list.set(1, "Ruby"); // 替换索引为 1 的元素为 "Ruby"

4. indexOf(Object o)

  • 说明: 查找元素 o 首次出现的位置。如果元素不存在,返回 -1。
  • 时间复杂度: (O(n))(线性搜索)。
  • 示例:
    1
    int index = list.indexOf("Ruby"); // 查找 "Ruby" 的索引

5. contains(Object o)

  • 说明: 检查列表是否包含指定元素 o
  • 时间复杂度: (O(n))(线性搜索)。
  • 示例:
    1
    boolean contains = list.contains("Java"); // 检查列表中是否包含 "Java"

6. clear()

  • 说明: 移除列表中的所有元素。
  • 时间复杂度: (O(n))(遍历列表)。
  • 示例:
    1
    list.clear(); // 清空列表中的所有元素

完整示例

下面是使用这些方法的完整示例,展示了如何创建一个 List 实例,并使用这些方法操作列表:

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
import java.util.ArrayList;
import java.util.List;

public class ListMethodsExample {
public static void main(String[] args) {
// 创建 List 实例
List<String> list = new ArrayList<>();

// 添加元素
list.add("Java");
list.add("Python");
list.add("JavaScript");
System.out.println("List: " + list); // 输出: [Java, Python, JavaScript]

// 获取元素
System.out.println("Element at index 1: " + list.get(1)); // 输出: Python

// 更新元素
list.set(1, "Ruby");
System.out.println("List after updating element at index 1: " + list); // 输出: [Java, Ruby, JavaScript]

// 查找元素
int index = list.indexOf("Ruby");
System.out.println("Index of 'Ruby': " + index); // 输出: 1

// 检查列表是否包含某个元素
boolean contains = list.contains("Java");
System.out.println("List contains 'Java': " + contains); // 输出: true

// 清空列表
list.clear();
System.out.println("List after clearing: " + list); // 输出: []
}
}

方法总结

  • add(E e): 在列表末尾添加元素。
  • get(int index): 获取指定索引的元素。
  • set(int index, E element): 替换指定位置的元素。
  • indexOf(Object o): 查找元素首次出现的位置。
  • contains(Object o): 检查列表是否包含指定元素。
  • clear(): 移除所有元素。

这些方法使得 List 成为一个非常灵活的数据结构,适用于需要动态大小和高效访问的场景。

4. List 的常见问题及优化

  • 性能:
    • ArrayList 适合随机访问,但在中间插入和删除较慢。
    • LinkedList 适合频繁插入和删除,但随机访问较慢。
  • 线程安全: ArrayListLinkedList 都不是线程安全的。如果需要线程安全的列表,可以使用 Collections.synchronizedList 方法来包装它们。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SynchronizedListExample {
public static void main(String[] args) {
// 创建线程安全的 ArrayList
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());

// 添加元素
synchronizedList.add("Thread-safe");
synchronizedList.add("List");

// 打印列表
System.out.println("SynchronizedList: " + synchronizedList);
}
}

List 接口在 Java 中提供了有序集合的功能。ArrayListLinkedList 是最常用的实现类,各自有不同的适用场景和性能特征。了解这些实现的特点和使用方法,有助于在合适的场景中选择正确的集合类型。

一些题目训练:

题目 1: 列表的排序和去重

描述: 给定一个包含重复元素的 ArrayList,对其进行排序,并去除重复的元素。

解决思路: 1. 使用 Set 去除重复元素。 2. 使用 Listsort 方法对去重后的列表进行排序。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class RemoveDuplicatesAndSort {
public static void main(String[] args) {
// 创建 ArrayList 实例
List<String> list = new ArrayList<>();
list.add("Banana");
list.add("Apple");
list.add("Banana");
list.add("Cherry");

// 去重
Set<String> set = new HashSet<>(list);
list = new ArrayList<>(set);

// 排序
Collections.sort(list);
System.out.println("Sorted list without duplicates: " + list);
}
}

题目 2: 使用 LinkedList 实现队列

描述: 使用 LinkedList 实现一个简单的队列,支持 enqueuedequeue 操作。

解决思路: 1. 使用 LinkedListaddLast 方法进行入队操作。 2. 使用 LinkedListremoveFirst 方法进行出队操作。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.LinkedList;
import java.util.Queue;

public class QueueUsingLinkedList {
public static void main(String[] args) {
// 创建 LinkedList 实例作为队列
Queue<String> queue = new LinkedList<>();

// 入队操作
queue.add("Element1");
queue.add("Element2");
queue.add("Element3");

// 打印队列
System.out.println("Queue: " + queue);

// 出队操作
String element = queue.poll();
System.out.println("Dequeued element: " + element);

// 打印队列
System.out.println("Queue after dequeue: " + queue);
}
}

题目 3: 在指定位置插入元素

描述: 在给定的 ArrayList 中的指定位置插入一个新元素,并打印插入后的列表。

解决思路: 1. 使用 ArrayListadd(index, element) 方法在指定位置插入元素。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.List;

public class InsertElementAtIndex {
public static void main(String[] args) {
// 创建 ArrayList 实例
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

// 在索引1处插入元素
list.add(1, "NewElement");
System.out.println("List after insertion: " + list);
}
}

题目 4: 从列表中删除特定元素

描述: 给定一个 ArrayList,删除所有特定的元素并打印结果。

解决思路: 1. 使用 ArrayListremove(Object o) 方法删除特定元素。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;
import java.util.List;

public class RemoveSpecificElement {
public static void main(String[] args) {
// 创建 ArrayList 实例
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
list.add("Banana");

// 删除所有 "Banana"
list.remove("Banana");
System.out.println("List after removing 'Banana': " + list);
}
}

题目 5: 获取列表的子列表

描述: 从给定的 ArrayList 中提取指定范围的子列表,并打印子列表。

解决思路: 1. 使用 ArrayListsubList(fromIndex, toIndex) 方法获取子列表。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.ArrayList;
import java.util.List;

public class GetSubList {
public static void main(String[] args) {
// 创建 ArrayList 实例
List<String> list = new ArrayList<>();
list.add("One");
list.add("Two");
list.add("Three");
list.add("Four");
list.add("Five");

// 获取索引1到3的子列表
List<String> subList = list.subList(1, 4);
System.out.println("SubList: " + subList);
}
}

题目 6: 反转列表

描述: 反转一个 ArrayList 的顺序并打印结果。

解决思路: 1. 使用 Collections.reverse 方法反转列表的顺序。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ReverseList {
public static void main(String[] args) {
// 创建 ArrayList 实例
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

// 反转列表
Collections.reverse(list);
System.out.println("Reversed list: " + list);
}
}

equals() 方法的编写指南

在 Java 中,equals() 方法用于比较对象是否相等。正确实现 equals() 方法对于确保集合(如 ListSet)能够正确识别和处理对象非常重要。

equals() 方法的要求

equals() 方法必须满足以下条件:

  1. 自反性(Reflexive): 对于非 nullxx.equals(x) 必须返回 true
  2. 对称性(Symmetric): 对于非 nullxy,如果 x.equals(y)true,则 y.equals(x) 也必须为 true
  3. 传递性(Transitive): 对于非 nullxyz,如果 x.equals(y)truey.equals(z) 也为 true,那么 x.equals(z) 也必须为 true
  4. 一致性(Consistent): 对于非 nullxy,只要 xy 的状态不变,则 x.equals(y) 总是一致地返回 truefalse
  5. null 的比较: x.equals(null) 永远返回 false

equals() 方法的实现步骤

  1. 定义相等的逻辑: 确定哪些字段应该用来判断两个对象是否相等。
  2. 检查类型: 使用 instanceof 关键字检查传入的对象是否是当前类的实例。
  3. 比较字段: 对于引用类型字段,使用 Objects.equals() 方法比较;对于基本类型字段,直接使用 == 比较。

示例实现

Person 类为例,假设 Person 类有三个字段:firstNamelastNameage。我们可以这样实现 equals() 方法:

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
import java.util.Objects;

public class Person {
String firstName;
String lastName;
int age;

public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}

@Override
public boolean equals(Object o) {
// 检查是否为当前类的实例
if (this == o) return true; // 自反性
if (!(o instanceof Person)) return false;
Person person = (Person) o;
// 对称性和传递性
return age == person.age &&
Objects.equals(firstName, person.firstName) &&
Objects.equals(lastName, person.lastName);
}

@Override
public int hashCode() {
// 正确实现 equals 方法时也应实现 hashCode 方法
return Objects.hash(firstName, lastName, age);
}
}

示例代码

下面是一个使用 Person 类的示例,测试 equals() 方法是否正确:

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Person> list = List.of(
new Person("Xiao", "Ming", 18),
new Person("Xiao", "Hong", 25),
new Person("Bob", "Smith", 20)
);
boolean exist = list.contains(new Person("Bob", "Smith", 20));
System.out.println(exist ? "测试成功!" : "测试失败!");
}
}

在这个示例中,虽然 new Person("Bob", "Smith", 20) 是一个新的实例,但由于 equals() 方法被正确实现,contains() 方法能够正确识别并返回 true

备注

  • hashCode() 方法: 在重写 equals() 方法时,也应该重写 hashCode() 方法,以确保相等对象具有相同的哈希码。
  • Objects.equals() 方法: 用于简化引用类型字段的比较,同时处理 null 值的情况。

通过正确实现 equals() 方法,可以确保 List 和其他集合类能够按预期工作,避免出现不必要的错误和问题。

Map

在 Java 中,Map 是一个用于存储键值对(key-value pairs)的集合。每个键(key)在 Map 中是唯一的,每个键对应一个值(value)。Map 接口的主要实现类包括 HashMapLinkedHashMapTreeMap

1. 常用 Map 实现类

  • HashMap: 基于哈希表实现,提供 O(1) 时间复杂度的插入、删除和查找操作。键和值均允许为 null
  • LinkedHashMap: 继承自 HashMap,保持插入顺序或访问顺序。插入和访问的时间复杂度为 O(1)。
  • TreeMap: 基于红黑树实现,按自然顺序或指定的比较器对键进行排序。时间复杂度为 O(log n) 的操作。

2. 基本操作

  • 创建 Map

    1
    2
    3
    Map<String, Integer> map = new HashMap<>();
    Map<String, Integer> linkedMap = new LinkedHashMap<>();
    Map<String, Integer> treeMap = new TreeMap<>();
  • 添加元素

    1
    2
    map.put("Apple", 1);
    map.put("Banana", 2);
  • 访问元素

    1
    Integer value = map.get("Apple"); // 返回 1
  • 删除元素

    1
    map.remove("Banana");
  • 检查键或值是否存在

    1
    2
    boolean hasKey = map.containsKey("Apple"); // true
    boolean hasValue = map.containsValue(2); // false
  • 获取键集合、值集合和键值对集合

    1
    2
    3
    Set<String> keys = map.keySet();
    Collection<Integer> values = map.values();
    Set<Map.Entry<String, Integer>> entries = map.entrySet();
  • 清空 Map

    1
    map.clear();
  • 获取 Map 的大小

    1
    int size = map.size();

3. 遍历 Map

  • 使用增强型 for 循环遍历键值对

    1
    2
    3
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
    }
  • 使用 forEach 方法

    1
    map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));
  • 遍历键集合

    1
    2
    3
    for (String key : map.keySet()) {
    System.out.println("Key: " + key + ", Value: " + map.get(key));
    }
  • 遍历值集合

    1
    2
    3
    for (Integer value : map.values()) {
    System.out.println("Value: " + value);
    }

4. 特殊操作

  • 合并值

    1
    map.merge("Apple", 5, Integer::sum); // 将 "Apple" 的值增加 5
  • 计算或获取值

    1
    2
    map.computeIfAbsent("Orange", key -> 3); // 如果 "Orange" 不存在,设置其值为 3
    map.computeIfPresent("Apple", (key, value) -> value + 1); // 如果 "Apple" 存在,将其值加 1
  • 替换值

    1
    2
    map.replace("Apple", 10); // 将 "Apple" 的值替换为 10
    map.replace("Apple", 10, 15); // 仅当 "Apple" 的值为 10 时,将其值替换为 15
  • 获取或设置默认值

    1
    Integer value = map.getOrDefault("Apple", 0); // 如果 "Apple" 不存在,返回默认值 0

5. 性能特征

  • HashMap: 提供 O(1) 平均时间复杂度的操作,但不保证元素的顺序。
  • LinkedHashMap: 提供 O(1) 时间复杂度的操作,并保持元素的插入顺序或访问顺序。
  • TreeMap: 提供 O(log n) 时间复杂度的操作,并保持元素的排序。

示例代码

以下是一个综合示例,展示了 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
45
46
47
48
49
50
51
52
53
import java.util.*;

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

// 添加元素
map.put("Apple", 1);
map.put("Banana", 2);
map.put("Cherry", 3);

// 访问元素
System.out.println("Value for 'Apple': " + map.get("Apple"));

// 删除元素
map.remove("Banana");

// 检查键或值是否存在
System.out.println("Contains 'Apple': " + map.containsKey("Apple"));
System.out.println("Contains value 2: " + map.containsValue(2));

// 获取键集合、值集合和键值对集合
System.out.println("Keys: " + map.keySet());
System.out.println("Values: " + map.values());
System.out.println("Entries: " + map.entrySet());

// 遍历 Map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}

// 使用 forEach 方法
map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));

// 合并值
map.merge("Apple", 5, Integer::sum);
System.out.println("After merge: " + map.get("Apple"));

// 计算或获取值
map.computeIfAbsent("Orange", key -> 3);
System.out.println("After computeIfAbsent: " + map.get("Orange"));

// 替换值
map.replace("Apple", 10);
map.replace("Apple", 10, 15);
System.out.println("After replace: " + map.get("Apple"));

// 获取或设置默认值
Integer value = map.getOrDefault("Banana", 0);
System.out.println("Default value for 'Banana': " + value);
}
}

hashCode 方法的编写指南

编写 hashCode 方法的指南涉及确保你的 hashCode 实现与 equals 方法兼容,并且符合 Java 的通用约定。

为什么要重写 hashCode 方法

hashCode 方法在 Java 中用于将对象映射到哈希表的桶(bucket)中。它与 equals 方法紧密相关:

  • 一致性: 如果两个对象根据 equals 方法是相等的,那么它们的 hashCode 方法必须返回相同的值。
  • 性能优化: 哈希表(如 HashMapHashSet)使用 hashCode 方法来快速查找、插入和删除元素。一个好的 hashCode 实现可以减少冲突,提高性能。

编写 hashCode 方法的基本准则

  1. 一致性: 如果对象的状态没有改变,多次调用 hashCode 方法应该返回相同的值。
  2. 相等性: 如果两个对象通过 equals 方法相等,那么它们的 hashCode 方法返回的值也应该相等。
  3. 唯一性: 虽然不是必须的,但 hashCode 的实现应尽量减少不同对象的哈希值冲突,以提高性能。

hashCode 方法的实现指南

1. 基本实现

使用对象的关键字段生成哈希值。这通常涉及以下步骤:

  1. 选择字段: 选择那些用来判断对象是否相等的字段(即 equals 方法中使用的字段)。
  2. 计算哈希值: 使用这些字段计算哈希值,通常使用 Objects.hash 方法或者直接使用字段的 hashCode 方法。
  3. 返回哈希值: 确保 hashCode 方法的实现符合一致性原则。

2. 示例实现

以下是一个简单的 Person 类的示例,包括 equalshashCode 方法的实现:

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
import java.util.Objects;

public class Person {
private String firstName;
private String lastName;
private int age;

public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}

@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(firstName, person.firstName) &&
Objects.equals(lastName, person.lastName);
}

@Override
public int hashCode() {
return Objects.hash(firstName, lastName, age);
}
}

3. 使用 Objects.hash

Objects.hash 是一个便利方法,它简化了使用对象字段计算哈希值的过程。例如:

1
2
3
4
@Override
public int hashCode() {
return Objects.hash(firstName, lastName, age);
}

4. 手动实现 hashCode

手动实现 hashCode 方法时,通常需要使用质数进行混合,以减少冲突的可能性。以下是一个简单的手动实现示例:

1
2
3
4
5
6
7
8
@Override
public int hashCode() {
int result = 17; // 初始质数
result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
result = 31 * result + age;
return result;
}

5. 避免使用可变字段

如果对象的字段是可变的,并且你使用这些字段来计算 hashCode,这可能会导致问题。例如,如果字段的值被修改,则对象的哈希值也会改变,这可能会破坏数据结构的完整性。在这些情况下,最好只使用不可变字段计算 hashCode

注意事项

  • 不应返回固定值: hashCode 方法应返回与对象状态相关的值,而不是固定值或常量。
  • 一致性: 确保 hashCode 方法与 equals 方法一致。两个相等的对象必须具有相同的哈希码。

EnumMap详解

EnumMap 是 Java Collections Framework 中的一种特殊类型的 Map,用于将枚举类型的枚举常量作为键。它具有一些特定的特点和用途。

1. EnumMap 的特点

  • 键类型: EnumMap 只接受枚举类型作为键。
  • 高效性: EnumMap 由于内部实现基于数组,操作效率比一般的 HashMap 更高。
  • 有序性: EnumMap 按照枚举常量的自然顺序来存储键值对。
  • null 键: EnumMap 不允许 null 作为键,如果尝试使用 null 键将抛出 NullPointerException
  • 实现: 它是 AbstractMap 的子类,并实现了 Serializable 接口。

2. 创建和初始化 EnumMap

要创建一个 EnumMap,首先需要定义一个枚举类型,然后使用该枚举类型作为 EnumMap 的键类型。可以通过不同的构造函数初始化 EnumMap

示例代码

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
import java.util.EnumMap;
import java.util.Map;

public class EnumMapExample {
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public static void main(String[] args) {
// 创建 EnumMap 实例
EnumMap<Day, String> schedule = new EnumMap<>(Day.class);

// 添加元素
schedule.put(Day.MONDAY, "Go to gym");
schedule.put(Day.WEDNESDAY, "Attend meeting");
schedule.put(Day.FRIDAY, "Work from home");

// 打印 EnumMap
System.out.println("Schedule: " + schedule);

// 访问元素
System.out.println("Monday's activity: " + schedule.get(Day.MONDAY));

// 删除元素
schedule.remove(Day.WEDNESDAY);
System.out.println("Schedule after removing Wednesday: " + schedule);

// 遍历 EnumMap
for (Map.Entry<Day, String> entry : schedule.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}

3. EnumMap 的操作方法

  • put(K key, V value): 插入或更新指定键的值。
  • get(Object key): 返回指定键的值。
  • remove(Object key): 删除指定键的映射。
  • containsKey(Object key): 检查 EnumMap 是否包含指定键。
  • containsValue(Object value): 检查 EnumMap 是否包含指定值。
  • size(): 返回 EnumMap 中键值对的数量。
  • clear(): 清空 EnumMap 中的所有键值对。
  • keySet(): 返回 EnumMap 中所有键的 Set 视图。
  • values(): 返回 EnumMap 中所有值的 Collection 视图。
  • entrySet(): 返回 EnumMap 中所有键值对的 Set 视图。

4. EnumMap 的优势和使用场景

  • 高效存储: EnumMap 提供了高效的键值对存储,尤其适合枚举类型作为键的场景。
  • 可读性和维护性: 使用枚举作为键可以使代码更具可读性和维护性,避免使用整数或字符串表示枚举常量。
  • 应用场景: 常用于需要映射枚举常量到某些值的场景,例如配置管理、状态机实现等。

5. 注意事项

  • 键的类型: EnumMap 只能使用枚举类型作为键。
  • 线程安全: EnumMap 不是线程安全的。如果在多线程环境中使用,需要额外的同步措施。

TreeMap

TreeMap 是 Java Collections Framework 中的一个实现 NavigableMap 接口的类,它基于红黑树(自平衡的二叉搜索树)实现。TreeMap 具有按自然顺序或自定义顺序对键进行排序的能力。

1. TreeMap 的特点

  • 排序: TreeMap 按照键的自然顺序或由构造函数提供的 Comparator 排序键。
  • 红黑树: 内部基于红黑树实现,这是一种自平衡的二叉搜索树,保证了操作的对数时间复杂度。
  • 不允许 null: TreeMap 不允许 null 键,但允许 null 值。尝试插入 null 键会抛出 NullPointerException
  • 线程安全: TreeMap 不是线程安全的。如果在多线程环境中使用,可能需要额外的同步措施。
  • 性能: 操作如插入、删除、查找、遍历的时间复杂度为 (O(n))。

2. 内部实现

  • 红黑树: TreeMap 使用红黑树的数据结构进行实现。红黑树是一种自平衡的二叉搜索树,通过对节点的颜色标记(红色或黑色)来维护平衡,确保所有基本操作的时间复杂度为 (O(n))。
  • 节点结构: 每个节点包含键值对、左子节点、右子节点和父节点的引用以及颜色信息。

3. 常用操作方法

插入和更新

  • put(K key, V value): 将键值对插入到 TreeMap 中。如果键已经存在,则更新其对应的值。
  • putAll(Map<? extends K, ? extends V> m): 将指定映射的所有键值对插入到 TreeMap 中。

查找

  • get(Object key): 返回指定键的值。如果键不存在,则返回 null
  • containsKey(Object key): 检查 TreeMap 是否包含指定的键。
  • containsValue(Object value): 检查 TreeMap 是否包含指定的值。

删除

  • remove(Object key): 删除指定键的映射。
  • clear(): 清空 TreeMap 中的所有键值对。

遍历

  • keySet(): 返回 TreeMap 中所有键的 Set 视图,按升序排列。
  • values(): 返回 TreeMap 中所有值的 Collection 视图,按照键的顺序排列。
  • entrySet(): 返回 TreeMap 中所有键值对的 Set 视图,按键的自然顺序排列。

导航操作

  • firstKey(): 返回 TreeMap 中的第一个键。
  • lastKey(): 返回 TreeMap 中的最后一个键。
  • ceilingKey(K key): 返回大于或等于指定键的最小键。如果不存在,则返回 null
  • floorKey(K key): 返回小于或等于指定键的最大键。如果不存在,则返回 null
  • higherKey(K key): 返回大于指定键的最小键。如果不存在,则返回 null
  • lowerKey(K key): 返回小于指定键的最大键。如果不存在,则返回 null
  • subMap(K fromKey, K toKey): 返回 TreeMap 中指定范围的视图。
  • headMap(K toKey): 返回 TreeMap 中小于指定键的视图。
  • tailMap(K fromKey): 返回 TreeMap 中大于或等于指定键的视图。

4. 示例代码

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
import java.util.TreeMap;
import java.util.Map;

public class TreeMapExample {
public static void main(String[] args) {
// 创建 TreeMap 实例
TreeMap<String, Integer> treeMap = new TreeMap<>();

// 添加元素
treeMap.put("Apple", 3);
treeMap.put("Banana", 2);
treeMap.put("Cherry", 5);

// 打印 TreeMap
System.out.println("TreeMap: " + treeMap);

// 查找元素
System.out.println("Value for 'Banana': " + treeMap.get("Banana"));

// 删除元素
treeMap.remove("Banana");
System.out.println("TreeMap after removing 'Banana': " + treeMap);

// 遍历 TreeMap
for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 导航操作
System.out.println("First key: " + treeMap.firstKey());
System.out.println("Last key: " + treeMap.lastKey());
System.out.println("Ceiling key for 'Blueberry': " + treeMap.ceilingKey("Blueberry"));
System.out.println("Floor key for 'Blueberry': " + treeMap.floorKey("Blueberry"));
System.out.println("Higher key than 'Apple': " + treeMap.higherKey("Apple"));
System.out.println("Lower key than 'Cherry': " + treeMap.lowerKey("Cherry"));

// 子地图
System.out.println("SubMap from 'Apple' to 'Cherry': " + treeMap.subMap("Apple", "Cherry"));
}
}

5. 注意事项

  • 键的比较: TreeMap 依赖键的比较操作。如果使用自然顺序,需要确保键实现了 Comparable 接口。如果使用自定义排序,需要提供一个 Comparator
  • 性能: TreeMap 的性能通常优于 HashMap,尤其在需要按排序顺序访问元素时。因为 TreeMap 保持元素的排序,而 HashMap 不提供任何顺序保证。
  • 线程安全: TreeMap 不是线程安全的。在多线程环境下使用时需要额外的同步措施。

实际应用例子

TreeMap 是一个非常有用的数据结构,在很多实际应用中都可以发挥作用。

1. 排名系统

假设我们在开发一个游戏或考试系统,需要根据成绩对玩家或学生进行排名。TreeMap 可以用来维护玩家或学生的排名列表,其中键为分数,值为玩家或学生的姓名。

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
import java.util.TreeMap;
import java.util.Map;

public class RankingSystem {
public static void main(String[] args) {
// 创建 TreeMap 实例,按分数降序排列
TreeMap<Integer, String> rankings = new TreeMap<>((a, b) -> b - a);

// 添加玩家及其分数
rankings.put(950, "Alice");
rankings.put(870, "Bob");
rankings.put(900, "Charlie");
rankings.put(920, "Dave");

// 打印排名
System.out.println("Rankings: " + rankings);

// 查找排名
System.out.println("Top scorer: " + rankings.firstEntry().getValue());
System.out.println("Lowest scorer: " + rankings.lastEntry().getValue());

// 查找排名区间
System.out.println("Scores between 880 and 920: " + rankings.subMap(880, true, 920, true));
}
}

2. 订单管理

在电商系统中,可以使用 TreeMap 来管理客户订单,按订单日期排序,便于进行时间范围内的订单查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.TreeMap;
import java.util.Map;
import java.time.LocalDate;

public class OrderManagement {
public static void main(String[] args) {
// 创建 TreeMap 实例,按日期升序排列
TreeMap<LocalDate, String> orders = new TreeMap<>();

// 添加订单及其日期
orders.put(LocalDate.of(2024, 8, 1), "Order1");
orders.put(LocalDate.of(2024, 8, 5), "Order2");
orders.put(LocalDate.of(2024, 8, 3), "Order3");

// 打印所有订单
System.out.println("Orders: " + orders);

// 查找特定日期的订单
System.out.println("Order on 2024-08-05: " + orders.get(LocalDate.of(2024, 8, 5)));

// 查找日期范围内的订单
System.out.println("Orders from 2024-08-01 to 2024-08-04: " + orders.subMap(LocalDate.of(2024, 8, 1), true, LocalDate.of(2024, 8, 4), true));
}
}

3. 任务调度

在任务调度系统中,可以使用 TreeMap 来按任务的到期时间进行排序。这样可以快速查找即将到期的任务,并进行优先级处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.TreeMap;
import java.util.Map;
import java.time.LocalDate;

public class TaskScheduler {
public static void main(String[] args) {
// 创建 TreeMap 实例,按到期日期升序排列
TreeMap<LocalDate, String> tasks = new TreeMap<>();

// 添加任务及其到期日期
tasks.put(LocalDate.of(2024, 8, 10), "Task1");
tasks.put(LocalDate.of(2024, 8, 15), "Task2");
tasks.put(LocalDate.of(2024, 8, 12), "Task3");

// 打印所有任务
System.out.println("Tasks: " + tasks);

// 查找即将到期的任务
System.out.println("Next task: " + tasks.firstEntry().getValue());

// 查找到期日期范围内的任务
System.out.println("Tasks from 2024-08-10 to 2024-08-14: " + tasks.subMap(LocalDate.of(2024, 8, 10), true, LocalDate.of(2024, 8, 14), true));
}
}

4. 会议日程管理

在会议管理系统中,可以使用 TreeMap 来管理会议日程,按会议时间排序,方便查看即将开始的会议和会议时间区间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.TreeMap;
import java.util.Map;
import java.time.LocalTime;

public class MeetingScheduler {
public static void main(String[] args) {
// 创建 TreeMap 实例,按时间升序排列
TreeMap<LocalTime, String> meetings = new TreeMap<>();

// 添加会议及其时间
meetings.put(LocalTime.of(9, 30), "Meeting with Team A");
meetings.put(LocalTime.of(11, 00), "Project Update");
meetings.put(LocalTime.of(14, 00), "Client Presentation");

// 打印所有会议
System.out.println("Meetings: " + meetings);

// 查找特定时间的会议
System.out.println("Meeting at 11:00: " + meetings.get(LocalTime.of(11, 0)));

// 查找时间范围内的会议
System.out.println("Meetings between 9:00 and 13:00: " + meetings.subMap(LocalTime.of(9, 0), true, LocalTime.of(13, 0), true));
}
}

5. 温度记录

在气象监测系统中,可以使用 TreeMap 来记录和分析温度数据,按日期排序,方便查找和处理特定日期的温度记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.TreeMap;
import java.util.Map;
import java.time.LocalDate;

public class TemperatureRecord {
public static void main(String[] args) {
// 创建 TreeMap 实例,按日期升序排列
TreeMap<LocalDate, Double> temperatureRecords = new TreeMap<>();

// 添加日期及其温度记录
temperatureRecords.put(LocalDate.of(2024, 8, 1), 32.5);
temperatureRecords.put(LocalDate.of(2024, 8, 2), 31.0);
temperatureRecords.put(LocalDate.of(2024, 8, 3), 30.8);

// 打印所有温度记录
System.out.println("Temperature Records: " + temperatureRecords);

// 查找特定日期的温度记录
System.out.println("Temperature on 2024-08-02: " + temperatureRecords.get(LocalDate.of(2024, 8, 2)));

// 查找日期范围内的温度记录
System.out.println("Temperature records from 2024-08-01 to 2024-08-02: " + temperatureRecords.subMap(LocalDate.of(2024, 8, 1), true, LocalDate.of(2024, 8, 2), true));
}
}

Set详解

Set 是 Java 集合框架中的一种集合类型,用于存储不重复的元素。

1. Set 接口

Set 是 Java 集合框架中的一个接口,继承自 Collection 接口。它的主要特点是:集合中的元素没有重复,且没有指定的顺序。

常用实现类

  • HashSet: 基于哈希表的实现,不保证元素的顺序。提供了常数时间的性能用于基本操作(添加、删除、包含),时间复杂度为 (O(1))。
  • LinkedHashSet: 继承自 HashSet,在哈希表的基础上使用链表保持元素的插入顺序。提供较好的插入顺序的遍历性能。
  • TreeSet: 基于红黑树的实现,元素按照自然顺序或构造时提供的比较器进行排序。提供对元素的排序操作,时间复杂度为 (O(n))。

2. 常用方法

添加和删除元素

  • add(E e): 添加元素。如果集合中已经存在该元素,则不会添加,并返回 false
  • remove(Object o): 删除指定元素。如果集合中存在该元素,则删除并返回 true,否则返回 false
  • clear(): 清空集合中的所有元素。

查找和检查

  • contains(Object o): 检查集合是否包含指定的元素。时间复杂度为 (O(1)) 对于 HashSet,(O(n)) 对于 TreeSet
  • isEmpty(): 检查集合是否为空。

其他方法

  • size(): 返回集合中元素的数量。
  • iterator(): 返回集合中元素的迭代器。
  • toArray(): 返回包含集合中所有元素的数组。

3. 应用场景

  • 去重: 用于存储唯一元素,如去重操作。
  • 集合运算: 用于集合的交集、并集、差集操作。例如:HashSet 可以用于高效的集合运算。
  • 排序: 如果需要按顺序存储元素并进行排序,则使用 TreeSet

4. 性能特点

  • HashSet:
    • 时间复杂度:添加、删除、查找操作的平均时间复杂度为 (O(1))。
    • 不保证元素的顺序。
  • LinkedHashSet:
    • 时间复杂度:添加、删除、查找操作的时间复杂度为 (O(1))。
    • 保持元素的插入顺序,适合需要迭代顺序的场景。
  • TreeSet:
    • 时间复杂度:添加、删除、查找操作的时间复杂度为 (O(n))。
    • 元素按自然顺序或提供的比较器排序,适合需要排序的场景。

5. 示例代码

使用 HashSet

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
import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
public static void main(String[] args) {
Set<String> set = new HashSet<>();

// 添加元素
set.add("Apple");
set.add("Banana");
set.add("Orange");

// 尝试添加重复元素
set.add("Apple");

// 输出集合
System.out.println("HashSet: " + set);

// 检查是否包含元素
System.out.println("Contains 'Banana': " + set.contains("Banana"));

// 删除元素
set.remove("Orange");
System.out.println("After removing 'Orange': " + set);

// 遍历集合
for (String fruit : set) {
System.out.println(fruit);
}
}
}

使用 LinkedHashSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetExample {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>();

// 添加元素
set.add("Apple");
set.add("Banana");
set.add("Orange");

// 尝试添加重复元素
set.add("Apple");

// 输出集合
System.out.println("LinkedHashSet: " + set);

// 遍历集合
for (String fruit : set) {
System.out.println(fruit);
}
}
}

使用 TreeSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.TreeSet;
import java.util.Set;

public class TreeSetExample {
public static void main(String[] args) {
Set<String> set = new TreeSet<>();

// 添加元素
set.add("Apple");
set.add("Banana");
set.add("Orange");

// 尝试添加重复元素
set.add("Apple");

// 输出集合(元素会按自然顺序排序)
System.out.println("TreeSet: " + set);

// 遍历集合
for (String fruit : set) {
System.out.println(fruit);
}
}
}

6. 注意事项

  • HashSetLinkedHashSet 中,元素的顺序是未定义的,而在 TreeSet 中,元素是有序的。
  • 在使用 TreeSet 时,如果自定义对象作为元素,需要确保对象实现了 Comparable 接口,或者在创建 TreeSet 时提供一个比较器。
  • Set 的操作通常是 (O(1)) 或 (O(n)),但要注意选择合适的实现类以满足性能需求和功能需求。

实际例子

1. HashSet 实际应用示例

场景: 去除重复的元素

示例: 从一组电话号码中去除重复的电话号码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
public static void main(String[] args) {
// 创建一个 HashSet 来存储电话号码
Set<String> phoneNumbers = new HashSet<>();

// 添加电话号码(包含重复项)
phoneNumbers.add("123-456-7890");
phoneNumbers.add("098-765-4321");
phoneNumbers.add("123-456-7890"); // 重复的电话号码
phoneNumbers.add("555-555-5555");

// 输出去重后的电话号码
System.out.println("Unique Phone Numbers: " + phoneNumbers);
}
}

输出:

1
Unique Phone Numbers: [555-555-5555, 123-456-7890, 098-765-4321]

解释: HashSet 自动去除了重复的电话号码,并且由于 HashSet 不保证元素的顺序,输出的顺序可能不一致。

2. LinkedHashSet 实际应用示例

场景: 维护元素的插入顺序

示例: 记录用户访问的网页URL,并保持访问顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetExample {
public static void main(String[] args) {
// 创建一个 LinkedHashSet 来记录访问的网页URL
Set<String> visitedURLs = new LinkedHashSet<>();

// 记录用户访问的网页(包含重复项)
visitedURLs.add("http://example.com/home");
visitedURLs.add("http://example.com/about");
visitedURLs.add("http://example.com/home"); // 访问相同页面
visitedURLs.add("http://example.com/contact");

// 输出访问的网页URL,保持插入顺序
System.out.println("Visited URLs: " + visitedURLs);
}
}

输出:

1
Visited URLs: [http://example.com/home, http://example.com/about, http://example.com/contact]

解释: LinkedHashSet 保持了插入元素的顺序,适用于需要按访问顺序记录信息的场景。

3. TreeSet 实际应用示例

场景: 按自然顺序排序元素

示例: 对一组学生的考试分数进行排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.TreeSet;
import java.util.Set;

public class TreeSetExample {
public static void main(String[] args) {
// 创建一个 TreeSet 来存储学生的考试分数
Set<Integer> examScores = new TreeSet<>();

// 添加考试分数
examScores.add(85);
examScores.add(92);
examScores.add(75);
examScores.add(88);
examScores.add(92); // 重复的分数

// 输出排序后的考试分数
System.out.println("Sorted Exam Scores: " + examScores);
}
}

输出:

1
Sorted Exam Scores: [75, 85, 88, 92]

解释: TreeSet 对分数进行了自然顺序的排序,适用于需要排序的数据结构,如考试分数、排名等。

Queue

Queue 是一种线性数据结构,遵循先进先出(FIFO,First In First Out)原则。即,队列中的元素是按照插入顺序排列的,且只有队首的元素可以被访问和移除。

主要操作

  1. offer(E e): 将元素 e 添加到队列的尾部。如果队列满,则返回 false
  2. add(E e): 将元素 e 添加到队列的尾部。如果队列满,则抛出 IllegalStateException
  3. poll(): 移除并返回队列的头部元素。如果队列为空,则返回 null
  4. remove(): 移除并返回队列的头部元素。如果队列为空,则抛出 NoSuchElementException
  5. peek(): 返回队列的头部元素但不移除。如果队列为空,则返回 null
  6. element(): 返回队列的头部元素但不移除。如果队列为空,则抛出 NoSuchElementException

实现类

  1. LinkedList: 实现了 Queue 接口,基于双向链表。支持所有 Queue 操作。
  2. PriorityQueue: 实现了 Queue 接口,基于优先级队列,元素根据自然排序或构造时提供的比较器排序。
  3. ArrayDeque: 实现了 Deque 接口(双端队列),支持队列操作,基于动态数组。通常性能优于 LinkedList
  4. ConcurrentLinkedQueue: 实现了 Queue 接口,基于非阻塞算法,适用于并发场景。

实际应用

1. 基于 LinkedList 的 Queue

LinkedList 可以作为队列使用,适用于需要频繁插入和删除操作的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.LinkedList;
import java.util.Queue;

public class LinkedListQueueExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();

// 添加元素
queue.offer("A");
queue.offer("B");
queue.offer("C");

// 打印队列
System.out.println("Queue: " + queue);

// 移除并打印队首元素
System.out.println("Removed: " + queue.poll());
System.out.println("Queue after poll: " + queue);

// 查看队首元素
System.out.println("Head of the queue: " + queue.peek());
}
}

2. 基于 PriorityQueue 的 Queue

PriorityQueue 可以用于按优先级处理元素,例如任务调度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueExample {
public static void main(String[] args) {
Queue<Integer> priorityQueue = new PriorityQueue<>();

// 添加元素
priorityQueue.offer(5);
priorityQueue.offer(1);
priorityQueue.offer(3);

// 打印队列
System.out.println("PriorityQueue: " + priorityQueue);

// 移除并打印队首元素
System.out.println("Removed: " + priorityQueue.poll());
System.out.println("PriorityQueue after poll: " + priorityQueue);

// 查看队首元素
System.out.println("Head of the queue: " + priorityQueue.peek());
}
}

3. 基于 ArrayDeque 的 Queue

ArrayDeque 提供了高效的队列操作,适用于需要双端操作的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.ArrayDeque;
import java.util.Queue;

public class ArrayDequeQueueExample {
public static void main(String[] args) {
Queue<String> queue = new ArrayDeque<>();

// 添加元素
queue.offer("X");
queue.offer("Y");
queue.offer("Z");

// 打印队列
System.out.println("Queue: " + queue);

// 移除并打印队首元素
System.out.println("Removed: " + queue.poll());
System.out.println("Queue after poll: " + queue);

// 查看队首元素
System.out.println("Head of the queue: " + queue.peek());
}
}
  • LinkedList: 适用于需要频繁插入和删除的队列操作。
  • PriorityQueue: 适用于按照优先级处理元素的场景。
  • ArrayDeque: 适用于高效的队列操作,尤其是在需要双端操作的场景下表现优异。
  • ConcurrentLinkedQueue: 适用于多线程环境中的并发队列操作。

PriorityQueue

PriorityQueue 是 Java 中实现优先级队列的数据结构,它遵循特定的优先级顺序处理元素。

PriorityQueue 是一个基于优先级的队列,其中的元素按自然顺序或自定义顺序排列。它不像传统的队列那样仅遵循先进先出的原则,而是根据优先级来决定元素的处理顺序。

主要特性

  1. 自然排序: 如果没有提供自定义比较器,PriorityQueue 会使用元素的自然顺序(即实现了 Comparable 接口的类)。
  2. 自定义排序: 可以通过提供 Comparator 实现自定义排序。
  3. 不允许 null 元素: PriorityQueue 不允许存储 null 元素,插入 null 会抛出 NullPointerException
  4. 不保证排序: PriorityQueue 保证队首元素是优先级最高的,但不保证其他元素的顺序。

常用方法

  • add(E e): 添加元素 e 到队列中。如果队列满,则抛出 IllegalStateException
  • offer(E e): 添加元素 e 到队列中。如果队列满,则返回 false
  • peek(): 返回队列的头部元素但不移除。如果队列为空,则返回 null
  • poll(): 移除并返回队列的头部元素。如果队列为空,则返回 null
  • remove(Object o): 移除队列中首次出现的指定元素。如果元素不存在,则返回 false
  • comparator(): 返回 PriorityQueue 使用的比较器。如果没有自定义比较器,返回 null
  • size(): 返回队列中元素的数量。

构造函数

  • PriorityQueue(): 使用默认的自然顺序。
  • PriorityQueue(Comparator<? super E> comparator): 使用指定的比较器。
  • PriorityQueue(int initialCapacity): 使用指定的初始容量。
  • PriorityQueue(Collection<? extends E> c): 使用指定集合的元素初始化队列。

使用示例

1. 自然排序

如果元素实现了 Comparable 接口,PriorityQueue 将按照元素的自然顺序进行排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.PriorityQueue;

public class NaturalOrderPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<>();

queue.offer(5);
queue.offer(1);
queue.offer(3);

System.out.println("PriorityQueue: " + queue); // 输出顺序可能不固定

System.out.println("Head of the queue: " + queue.peek());
System.out.println("Removed: " + queue.poll());
System.out.println("Queue after poll: " + queue);
}
}

2. 自定义排序

使用自定义 ComparatorPriorityQueue 进行排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.PriorityQueue;
import java.util.Comparator;

public class CustomOrderPriorityQueue {
public static void main(String[] args) {
// 创建一个优先级队列,按降序排列
PriorityQueue<Integer> queue = new PriorityQueue<>(Comparator.reverseOrder());

queue.offer(5);
queue.offer(1);
queue.offer(3);

System.out.println("PriorityQueue: " + queue); // 输出顺序可能不固定

System.out.println("Head of the queue: " + queue.peek());
System.out.println("Removed: " + queue.poll());
System.out.println("Queue after poll: " + queue);
}
}

3. 使用自定义对象

为自定义对象实现 Comparable 接口或提供 Comparator 进行排序。

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
import java.util.PriorityQueue;
import java.util.Comparator;

class Person {
String name;
int age;

Person(String name, int age) {
this.name = name;
this.age = age;
}

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

public class PersonPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Person> queue = new PriorityQueue<>(Comparator.comparingInt(p -> p.age));

queue.offer(new Person("Alice", 30));
queue.offer(new Person("Bob", 25));
queue.offer(new Person("Charlie", 35));

System.out.println("PriorityQueue: " + queue);

System.out.println("Head of the queue: " + queue.peek());
System.out.println("Removed: " + queue.poll());
System.out.println("Queue after poll: " + queue);
}
}

性能和复杂度

  • 插入操作: O(log n),因为需要维持堆的性质。
  • 删除操作: O(log n),移除队首元素并重排堆。
  • 访问队首元素: O(1),队首元素是优先级最高的元素。
  • PriorityQueue 提供了一个灵活的优先级排序机制,通过自然顺序或自定义比较器来处理元素。
  • 适用于需要按照优先级处理任务的场景,如任务调度、事件驱动等。
  • 由于不支持 null 元素,因此在插入时需要注意。
  • PriorityQueue 不保证整个队列的顺序,只保证队首元素是优先级最高的。

Deque

Deque(双端队列)是 Java 中提供的接口,允许在队列的两端进行插入和删除操作。Deque 继承自 Queue 接口,并提供了比普通队列更多的操作方法。

Deque 是 "Double Ended Queue" 的缩写,表示一个双端队列。它允许在队列的两端插入和删除元素,这与标准队列(只能从一端插入,一端删除)不同。Deque 接口扩展了 Queue 接口,提供了更多的操作方法。

主要特性

  1. 双端操作: 可以在队列的头部和尾部进行插入和删除操作。
  2. 无界或有界: 可以是无界(如 ArrayDeque)或有界(如 LinkedList)。
  3. 线程不安全: Deque 的标准实现(如 ArrayDequeLinkedList)不是线程安全的。如果需要线程安全的版本,可以考虑使用 ConcurrentLinkedDeque

常用方法

Deque 接口提供了许多方法用于在双端进行操作,主要包括:

插入方法

  • addFirst(E e): 在队列的头部插入元素 e
  • addLast(E e): 在队列的尾部插入元素 e
  • offerFirst(E e): 尝试在队列的头部插入元素 e,如果成功返回 true,否则返回 false
  • offerLast(E e): 尝试在队列的尾部插入元素 e,如果成功返回 true,否则返回 false

删除方法

  • removeFirst(): 移除并返回队列的头部元素。
  • removeLast(): 移除并返回队列的尾部元素。
  • pollFirst(): 移除并返回队列的头部元素,如果队列为空则返回 null
  • pollLast(): 移除并返回队列的尾部元素,如果队列为空则返回 null

访问方法

  • getFirst(): 返回队列的头部元素但不移除。
  • getLast(): 返回队列的尾部元素但不移除。
  • peekFirst(): 返回队列的头部元素但不移除,如果队列为空则返回 null
  • peekLast(): 返回队列的尾部元素但不移除,如果队列为空则返回 null

实现类

Deque 接口有两个主要的实现类:

  1. ArrayDeque: 基于动态数组实现的双端队列,性能较好,适合用作栈和队列。
  2. LinkedList: 基于链表实现的双端队列,提供了双端操作,适合需要频繁插入和删除的场景。

使用示例

1. 使用 ArrayDeque

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
import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeExample {
public static void main(String[] args) {
Deque<String> deque = new ArrayDeque<>();

// 添加元素
deque.addFirst("A");
deque.addLast("B");
deque.addLast("C");

System.out.println("Deque after additions: " + deque);

// 访问头部和尾部元素
System.out.println("First element: " + deque.getFirst());
System.out.println("Last element: " + deque.getLast());

// 删除元素
deque.removeFirst();
deque.removeLast();

System.out.println("Deque after removals: " + deque);

// 插入元素到队列两端
deque.offerFirst("D");
deque.offerLast("E");

System.out.println("Deque after offers: " + deque);
}
}

2. 使用 LinkedList

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
import java.util.Deque;
import java.util.LinkedList;

public class LinkedListExample {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<>();

// 添加元素
deque.addFirst("A");
deque.addLast("B");
deque.addLast("C");

System.out.println("Deque after additions: " + deque);

// 访问头部和尾部元素
System.out.println("First element: " + deque.peekFirst());
System.out.println("Last element: " + deque.peekLast());

// 删除元素
deque.pollFirst();
deque.pollLast();

System.out.println("Deque after removals: " + deque);

// 插入元素到队列两端
deque.offerFirst("D");
deque.offerLast("E");

System.out.println("Deque after offers: " + deque);
}
}

性能和复杂度

  • 插入和删除操作:
    • ArrayDeque: O(1) 时间复杂度,对于头部和尾部操作都非常高效。
    • LinkedList: O(1) 时间复杂度,对于头部和尾部操作也很高效,但相对而言,ArrayDeque 由于其动态数组的特点可能会更好。
  • 访问操作:
    • ArrayDeque: O(1) 时间复杂度,访问队列的头部和尾部元素效率高。
    • LinkedList: O(1) 时间复杂度,同样高效,但由于其链表结构,内存占用较高。
  • Deque 是一个非常灵活的数据结构,适合需要双端操作的场景。
  • ArrayDequeLinkedList 都是 Deque 的实现类,各有优缺点。ArrayDeque 在性能上可能更具优势,而 LinkedList 提供了更全面的链表操作。
  • Deque 的多样操作使其适合用于实现栈、队列等数据结构的各种变种。

Stack

Stack 是 Java 中提供的一种后进先出(LIFO,Last-In-First-Out)数据结构。它允许在栈的顶端进行元素的添加和移除操作。以下是有关 Stack 的详细笔记,包括其基本概念、方法、实现类、使用示例及实际应用。

Stack 是一种特殊的线性数据结构,其中的元素遵循后进先出原则。最后添加的元素会最先被移除。栈通常用于实现递归、回溯算法、解析表达式等场景。

主要特性

  1. 后进先出: 最近插入的元素最先被移除。
  2. 单端操作: 所有的插入和删除操作都在栈的顶端进行。
  3. 支持动态扩展: 默认实现是基于 Vector 类的动态数组,可以根据需要调整大小。

常用方法

Stack 类提供了多种方法用于栈操作,主要包括:

基本操作

  • push(E e): 将元素 e 压入栈顶。
  • pop(): 移除并返回栈顶的元素。
  • peek(): 返回栈顶的元素但不移除。
  • isEmpty(): 检查栈是否为空。
  • size(): 返回栈中元素的数量。

示例代码

以下是使用 Stack 的基本示例:

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
import java.util.Stack;

public class StackExample {
public static void main(String[] args) {
// 创建 Stack 实例
Stack<String> stack = new Stack<>();

// 压入元素
stack.push("A");
stack.push("B");
stack.push("C");

System.out.println("Stack after pushes: " + stack);

// 查看栈顶元素
String top = stack.peek();
System.out.println("Top element: " + top);

// 移除栈顶元素
String removed = stack.pop();
System.out.println("Removed element: " + removed);

System.out.println("Stack after pop: " + stack);

// 检查栈是否为空
boolean isEmpty = stack.isEmpty();
System.out.println("Is stack empty? " + isEmpty);

// 获取栈的大小
int size = stack.size();
System.out.println("Stack size: " + size);
}
}

实现类

StackVector 的子类,因此它的底层实现是基于 Vector 的动态数组。其常见实现包括:

  • java.util.Stack: 标准的栈实现,基于 Vector 实现,提供了栈的基本操作。

性能和复杂度

  • push(E e): O(1) 时间复杂度,在栈顶添加元素。
  • pop(): O(1) 时间复杂度,从栈顶移除元素。
  • peek(): O(1) 时间复杂度,查看栈顶元素。
  • isEmpty(): O(1) 时间复杂度,检查栈是否为空。
  • size(): O(1) 时间复杂度,获取栈的大小。

实际应用

  1. 递归和回溯: 栈可以用来模拟递归调用,帮助实现深度优先搜索(DFS)等回溯算法。
  2. 表达式求值: 用于解析和计算表达式,如中缀表达式转后缀表达式,或计算后缀表达式的值。
  3. 浏览器历史记录: 用于管理用户在网页浏览中的历史记录,用户可以使用“后退”按钮回到之前的页面。
  • Stack 是一个经典的后进先出(LIFO)数据结构,主要用于处理需要后进先出操作的场景。
  • Stack 提供了基本的栈操作,如 pushpoppeek 等,所有操作都在栈顶进行。
  • Stack 的底层实现基于 Vector,支持动态扩展。
  • 在现代 Java 编程中,Deque(如 ArrayDeque)也可以用于实现栈,提供了更高效的操作和更少的内存开销。

Iterator

Iterator 是 Java 集合框架中的一个重要接口,提供了一种统一的方法来遍历集合中的元素。它允许开发者通过标准的方式访问集合中的元素,而无需暴露集合的内部结构。

Iterator 是一个用于遍历集合元素的接口,提供了基本的方法来访问集合中的元素。以下是 Iterator 接口的主要方法及其功能:

主要方法

  • boolean hasNext(): 检查是否还有更多元素可以遍历。如果集合中还有未遍历的元素,返回 true;否则,返回 false
  • E next(): 返回集合中的下一个元素。调用此方法之前应确保 hasNext() 返回 true
  • void remove(): 从集合中移除 next() 方法返回的最后一个元素。注意,调用 remove() 方法之前必须调用 next() 方法,否则会抛出 IllegalStateException 异常。

使用示例

以下是使用 Iterator 遍历 ArrayList 的示例:

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
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorExample {
public static void main(String[] args) {
// 创建 List 实例
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");

// 获取 Iterator 实例
Iterator<String> iterator = list.iterator();

// 遍历集合
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("Element: " + element);

// 可以使用 remove() 方法删除元素
if ("Banana".equals(element)) {
iterator.remove();
}
}

System.out.println("List after removal: " + list);
}
}

注意事项

  1. ConcurrentModificationException:
    • 当使用 Iterator 遍历集合时,如果在遍历过程中对集合进行了结构性修改(例如,使用集合的 addremove 方法),则会抛出 ConcurrentModificationException。为了避免这种情况,应该只通过 Iteratorremove() 方法来删除元素。
  2. 安全的遍历:
    • Iterator 是线程不安全的,因此在多线程环境中使用时,需要进行适当的同步操作。如果需要线程安全的迭代,可以考虑使用 Collections.synchronizedListCopyOnWriteArrayList 等线程安全的集合实现。
  3. 支持集合接口:
    • 所有实现了 Collection 接口的集合类都支持 Iterator,例如 ArrayListHashSetLinkedList 等。

性能

  • hasNext(): O(1) 时间复杂度,检查是否还有下一个元素。
  • next(): O(1) 时间复杂度,获取下一个元素。
  • remove(): O(1) 时间复杂度,从集合中移除最后一个返回的元素(仅在支持的集合中有效)。

实际应用

  1. 集合遍历:
    • Iterator 提供了一种通用的方式来遍历集合中的元素,而无需关心集合的具体实现。
  2. 集合的安全删除:
    • 使用 Iteratorremove() 方法可以在遍历集合的同时安全地删除元素,而避免 ConcurrentModificationException
  3. 自定义集合类:
    • 实现自定义集合类时,可以提供自定义的 Iterator 实现,以支持集合的自定义遍历逻辑。
  • Iterator 是一个用于遍历集合的接口,提供了标准化的遍历集合元素的方法。
  • 主要方法包括 hasNext()next()remove(),这些方法允许安全地访问和操作集合中的元素。
  • 在使用 Iterator 时,应注意避免并发修改,并合理使用 remove() 方法进行元素删除。
  • Iterator 使得对集合元素的遍历变得更加灵活和一致,是 Java 集合框架中的重要组成部分。

实际例子:

Iterator 接口在 Java 集合框架中用于统一地遍历集合中的元素。

1. 遍历并处理集合元素

在处理集合中的每个元素时,使用 Iterator 可以保证代码的清晰和一致性。以下示例展示了如何遍历一个 List 并对每个元素执行某些操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorExample {
public static void main(String[] args) {
// 创建一个 List 实例
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");

// 获取 Iterator 实例
Iterator<String> iterator = list.iterator();

// 遍历并处理集合元素
while (iterator.hasNext()) {
String element = iterator.next();
// 打印元素长度
System.out.println("Length of " + element + ": " + element.length());
}
}
}

2. 在遍历过程中删除特定元素

Iterator 提供的 remove() 方法允许在遍历过程中安全地删除集合中的元素。以下示例演示了如何删除所有长度小于 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
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class RemoveShortStrings {
public static void main(String[] args) {
// 创建一个 Set 实例
Set<String> set = new HashSet<>();
set.add("Short");
set.add("Longer");
set.add("VeryLongString");

// 获取 Iterator 实例
Iterator<String> iterator = set.iterator();

// 遍历并删除长度小于 6 的字符串
while (iterator.hasNext()) {
String element = iterator.next();
if (element.length() < 6) {
iterator.remove();
}
}

System.out.println("Set after removal: " + set);
}
}

3. 自定义迭代器遍历自定义集合

实现自定义集合类时,可以自定义 Iterator 实现,以支持特定的遍历逻辑。以下示例展示了如何实现一个简单的自定义集合和其迭代器:

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
import java.util.Iterator;
import java.util.NoSuchElementException;

public class SimpleCollection {
private Object[] elements;
private int size;

public SimpleCollection(int capacity) {
elements = new Object[capacity];
size = 0;
}

public void add(Object element) {
if (size >= elements.length) {
throw new IllegalStateException("Collection is full");
}
elements[size++] = element;
}

public Iterator<Object> iterator() {
return new SimpleIterator();
}

private class SimpleIterator implements Iterator<Object> {
private int index = 0;

@Override
public boolean hasNext() {
return index < size;
}

@Override
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return elements[index++];
}

@Override
public void remove() {
throw new UnsupportedOperationException();
}
}

public static void main(String[] args) {
SimpleCollection collection = new SimpleCollection(5);
collection.add("Element 1");
collection.add("Element 2");
collection.add("Element 3");

Iterator<Object> iterator = collection.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

4. 使用 Iterator 遍历 MapentrySet

Iterator 也可以用于遍历 Map 的条目(键值对)。以下示例演示了如何遍历 MapentrySet 并打印每个键值对:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapIteratorExample {
public static void main(String[] args) {
// 创建一个 Map 实例
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 30);
map.put("Bob", 25);
map.put("Charlie", 35);

// 获取 Iterator 实例
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();

// 遍历并处理 Map 条目
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}

5. 遍历 Queue 中的元素

Iterator 也可用于遍历 Queue 的元素,尽管 Queue 接口本身不定义 Iterator,但其实现类如 LinkedListPriorityQueue 都提供了 Iterator。以下示例演示了如何遍历 PriorityQueue 的元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.PriorityQueue;
import java.util.Iterator;

public class PriorityQueueExample {
public static void main(String[] args) {
// 创建 PriorityQueue 实例
PriorityQueue<String> queue = new PriorityQueue<>();
queue.add("Task 1");
queue.add("Task 2");
queue.add("Task 3");

// 获取 Iterator 实例
Iterator<String> iterator = queue.iterator();

// 遍历并打印队列元素
while (iterator.hasNext()) {
System.out.println("Queue element: " + iterator.next());
}
}
}