1、Java基础面试题问题
- 问题 1. 简述 Java 集合类都有哪些?
- 问题 2. 简述 Collection 与 Collections 的区别
- 问题 3. 简述 List、Set、Map 三者的区别
- 问题 4. 介绍一下 ArrayList 的底层结构和相关原理
- 问题 5. 介绍一下 ArrayList 的扩容机制
- 问题 6. 介绍一下 ArrayList 删除元素时有哪些顾虑?
- 问题 7. 介绍一下 ArrayList 中怎么在遍历移除一个元素?
- 问题 8. 介绍一下 ArrayList 是线程安全的吗?如何保证 ArrayList 的线程安全?
- 问题 9. 简述 ArrayList 和 LinkedList 的区别
- 问题 10. 简述 ArrayList 和 Vector 的区别
- 问题 11. 简述 ArrayList 与 Array 的区别
- 问题 12. 介绍一下 LinkedList 的底层结构和相关原理
- 问题 13. 介绍一下 Vector 的底层结构和相关原理
- 问题 14. 介绍一下 Stack 的底层结构和相关原理
- 问题 15. 请解释一下 Java 中的 CopyOnWriteArrayList
- 问题 16. 简述队列和栈是什么,它们有何区别?
- 问题 17. 请解释一下 Java 中的 Queue 和 Deque?
- 问题 18. 请解释一下 Java 中的 PriorityQueue?
- 问题 19. 请解释一下 Java 中的 BlockingQueue?
- 问题 20. 介绍一下 HashSet 的底层结构和相关原理
- 问题 21. 介绍一下 LinkedHashSet 的底层结构和相关原理
- 问题 22. 介绍一下 TreeSet 的底层结构和相关原理
- 问题 23. 请解释一下 Java 中的 EnumSet
- 问题 24. 请解释一下 Java 中的 SortedSet
- 问题 25. 请解释一下 Java 中的 NavigableSet
2、Java基础面试题解答
2.1、Java集合架构相关
- 问题 1. 简述 Java 集合类都有哪些?
解答:Java 集合类呢主要是指 java.Util包 下的集合容器。主要包含三种:List、Set、Map,其中 List、Set 主要继承自 Collection 接口,然后它们三个又都依赖了 Iterator 迭代器;
- 首先,List 常用的就是 ArrayList、LinkedList、Vector,其中 ArrayList 的底层实现是数组,LinkedList 的实现是双向链表,此外 LinkedList 还实现了 Queue 队列(也在 Collection 下的接口),Vector 就是 ArrayList 的线程安全版本,但不推荐使用,此外 Java 中的栈 Stack 还继承自 Vector;
- 其次,Set 集合常用的就是 HashSet 和 TreeSet,它们的实现就是依赖于 HahsMap 和 TreeMap;
- 最后,Map 集合就是 HahsMap 和 TreeMap了。这些实现大多数都是非线程安全的。
- 问题 2. 简述 Collection 与 Collections 的区别
解答:Collection
和 Collections
在 Java 中是两个不同的概念。
-
Collection
是一个接口,它是 List、Set 和 Queue 接口的父接口,定义了适用于任何集合的操作,如 add、remove 和 contains。 -
Collections
是一个类,它包含了一些静态的工具方法,这些方法可以对集合进行操作,如排序(sort)、查找(binarySearch)、修改(fill、copy)、同步控制(synchronizedXxx)等。
- 问题 3. 简述 List、Set、Map 三者的区别
解答:List
、Set
和 Map
是 Java 集合框架中的三种基本接口,它们的区别主要体现在存储内容和使用方式上。
-
List
:是一个有序的集合,可以包含重复的元素。它提供了索引的访问方式,我们可以通过索引(列表的位置)来访问或者搜索列表中的元素。主要实现类有 ArrayList、LinkedList 和 Vector。 -
Set
:是一个不允许有重复元素的集合,也就是说,每个元素只能出现一次。它不提供索引访问方式,主要用于存在性检查,即检查一个元素是否存在。主要实现类有 HashSet、LinkedHashSet 和 TreeSet。 -
Map
:是一个键值对的集合,每个键映射到一个值,键不能重复,每个键最多只能映射到一个值。它提供了基于键的访问方式,我们可以通过键来获取、删除或者检查值。主要实现类有 HashMap、LinkedHashMap、TreeMap 和 Hashtable。
2.2、JavaList集合相关
- 问题 4. 介绍一下 ArrayList 的底层结构和相关原理
解答:ArrayList
是 Java 中常用的一种动态数组实现,其底层是基于数组实现的。
-
存储结构:
ArrayList
内部使用一个数组(elementData
)来存储元素。当添加元素时,如果数组已满,就会创建一个新的更大的数组,并将原数组的内容复制到新数组中,这个过程称为扩容。 -
扩容机制:
ArrayList
的扩容机制是,每次扩容时,新数组的大小是原数组大小的 1.5 倍。如果这个值仍然不足以满足需求,那么新数组的大小就直接设置为需求的大小。 -
插入和删除:
ArrayList
在尾部插入和删除元素非常高效,时间复杂度为 O(1)。但是在中间或头部插入和删除元素需要移动大量元素,时间复杂度为 O(n)。 -
访问元素:由于底层是数组,所以
ArrayList
支持随机访问,按索引访问元素的时间复杂度为 O(1)。 -
线程安全:
ArrayList
是非线程安全的,如果需要在多线程环境下使用,可以使用Collections.synchronizedList()
方法返回一个线程安全的ArrayList
,或者使用线程安全的Vector
。
总的来说,ArrayList
是一种动态数组结构,适合随机访问场景,但在中间或头部插入和删除元素时效率较低。
- 问题 5. 介绍一下 ArrayList 的扩容机制
解答:ArrayList
的扩容机制是这样的:
-
当我们向
ArrayList
添加元素时,如果当前数组已满(即数组的大小等于其元素的数量),那么ArrayList
就需要进行扩容。 -
ArrayList
的扩容过程是创建一个新的数组,这个新数组的大小是原数组大小的 1.5 倍(即原数组大小加上原数组大小的一半)。具体来说,如果原数组大小为 10,那么新数组的大小就是 15。 -
创建新数组后,
ArrayList
会将原数组中的所有元素复制到新数组中,然后丢弃原数组。 -
这个扩容过程是自动进行的,我们在使用
ArrayList
时无需关心其扩容机制。
需要注意的是,ArrayList
的这种扩容机制意味着其在添加大量元素时可能会有一定的性能开销,因为每次扩容都需要创建新数组并复制元素。如果我们预先知道 ArrayList
将要存储的元素数量,可以在创建 ArrayList
时指定其初始大小,这样可以减少扩容操作,提高性能。
- 问题 6. 介绍一下 ArrayList 删除元素时有哪些顾虑?
解答:在使用 ArrayList
删除元素时,需要注意以下几点:
-
ConcurrentModificationException
:在遍历ArrayList
的过程中直接调用ArrayList
的remove()
方法删除元素,可能会抛出ConcurrentModificationException
异常。正确的做法是使用Iterator
的remove()
方法删除元素。 -
性能考虑:
ArrayList
在删除元素时,为了保持元素的连续性,会将后面的元素向前移动,所以删除元素的性能与ArrayList
的大小成正比。如果需要频繁删除元素,可以考虑使用LinkedList
。 -
索引越界:在调用
remove(int index)
方法时,如果索引超出了ArrayList
的范围,会抛出IndexOutOfBoundsException
异常。所以在删除元素前,需要确保索引是有效的。 -
自动装箱:
ArrayList
的remove()
方法有两个重载版本:remove(int index)
和remove(Object o)
。如果ArrayList
存储的是基本类型的包装类,比如Integer
,那么在调用remove()
方法时需要注意自动装箱可能带来的问题。例如,list.remove(1)
可能不是删除元素1
,而是删除索引为1
的元素。 -
空指针:如果
ArrayList
中存储的是对象,那么在删除元素时,如果ArrayList
中存在null
,需要注意NullPointerException
异常。
- 问题 7. 介绍一下 ArrayList 中怎么在遍历移除一个元素?
解答:在遍历 ArrayList
时移除元素,需要注意 ConcurrentModificationException
异常。以下是几种常见的移除元素的方法:
- 使用
Iterator
的remove()
方法。这是最安全且推荐的方式。
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (需要移除的条件) {
iterator.remove();
}
}
- 使用
List
的remove()
方法,但需要倒序遍历。
for (int i = list.size() - 1; i >= 0; i--) {
if (需要移除的条件) {
list.remove(i);
}
}
- 使用 Java 8 的
Collection.removeIf()
方法。
list.removeIf(item -> 需要移除的条件);
以上三种方法都可以在遍历 ArrayList
时移除元素,但是推荐使用 Iterator
的 remove()
方法,因为它在面对并发修改时可以提供正确的行为。
- 问题 8. 介绍一下 ArrayList 是线程安全的吗?如何保证 ArrayList 的线程安全?
解答:ArrayList
是非线程安全的,它的方法没有进行同步处理,所以在多线程环境下可能会出现问题。
如果需要在多线程环境下使用 ArrayList
,有以下几种方式可以保证线程安全:
- 使用
Collections.synchronizedList()
方法创建一个同步的ArrayList
:
List<String> list = Collections.synchronizedList(new ArrayList<>());
这个方法会返回一个线程安全的 List
,所有的方法都进行了同步处理。
- 使用
CopyOnWriteArrayList
,这是一个线程安全的List
实现:
List<String> list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList
在每次修改操作(如添加、删除元素)时,都会复制一份新的数组,这样可以避免修改操作对遍历操作的影响,提供了一种读写分离的机制。
-
使用并发包
java.util.concurrent
中的其他线程安全集合,如ConcurrentLinkedQueue
、ConcurrentHashMap
等。 -
使用
synchronized
关键字或Lock
对象手动同步ArrayList
的操作。
需要注意的是,以上方法在提供线程安全的同时,可能会带来一定的性能开销。
- 问题 9. 简述 ArrayList 和 LinkedList 的区别
解答:ArrayList
和 LinkedList
都是 List
接口的实现,但它们在内部数据结构和性能上有一些区别:
- 内部数据结构:
ArrayList
是基于动态数组实现的,支持随机访问,按索引访问元素非常快,时间复杂度为 O(1)。LinkedList
是基于双向链表实现的,不支持高效的随机访问,按索引访问元素需要从头(或尾)开始遍历,时间复杂度为 O(n)。 - 插入和删除:
ArrayList
的插入和删除操作需要进行数组元素的移动(除非插入和删除操作在列表末尾进行),所以插入和删除元素的时间复杂度为 O(n)。LinkedList
的插入和删除操作只需要改变节点的引用,所以在列表中间插入和删除元素的时间复杂度为 O(1)(前提是已经获取到了要插入位置的节点)。 - 内存占用:
ArrayList
的内存占用相对较低,因为它只需要存储元素数据和数组的引用。LinkedList
的内存占用较高,因为它需要额外存储节点之间的引用。
总的来说,ArrayList
更适合随机访问场景,LinkedList
更适合插入和删除操作频繁的场景。
- 问题 10. 简述 ArrayList 和 Vector 的区别
解答:ArrayList
和 Vector
都是实现了 List
接口的类,它们都是基于动态数组实现的,但是在同步性和性能上有一些区别:
- 同步性:
Vector
是线程安全的,它的大部分方法都进行了同步处理,可以在多线程环境下使用。实现上其实就是 Vector 在 ArrayList 的方法前面加上了 Synchronized。ArrayList
是非线程安全的,它的方法没有进行同步处理,所以在多线程环境下可能会出现问题。 - 性能:由于
Vector
进行了同步处理,所以在单线程环境下,Vector
的性能会比ArrayList
差一些。ArrayList
在单线程环境下的性能比Vector
好,因为它没有进行同步处理。 - 扩容:
ArrayList
在每次需要扩容时,都会增加到原来的 1.5 倍。Vector
在每次需要扩容时,都会增加到原来的 2 倍。
总的来说,如果需要在多线程环境下使用,可以选择 Vector
,如果是在单线程环境下,或者已经通过其他方式处理了同步问题,那么 ArrayList
会是更好的选择。
- 问题 11. 简述 ArrayList 与 Array 的区别
解答:Array
和 ArrayList
是 Java 中两种不同的数据结构,它们的主要区别在于大小的可变性、性能、类型限制和功能。
- 大小可变性:
Array
是固定长度的,一旦创建,其大小就不能改变。ArrayList
是动态的,可以自动调整其大小以适应元素的添加和删除。 - 性能:
Array
在访问元素时具有更好的性能,因为它是基于索引的数据结构。ArrayList
在添加和删除元素时具有更好的性能,特别是在列表的末尾,因为它可以动态调整大小。 - 类型限制:
Array
可以存储基本数据类型或对象。ArrayList
只能存储对象,不能直接存储基本数据类型。 - 功能:
Array
是一个简单的数据结构,没有提供很多功能。ArrayList
是一个集合类,提供了大量的方法,如添加、删除、遍历等。
总的来说,Array
和 ArrayList
各有优势,选择哪种取决于具体的需求。
- 问题 12. 介绍一下 LinkedList 的底层结构和相关原理
解答:LinkedList
是 Java 中常用的一种链表实现,其底层是基于双向链表实现的。
-
存储结构:
LinkedList
内部使用一个双向链表来存储元素。每个元素(节点)都包含了对前一个元素和后一个元素的引用。 -
插入和删除:
LinkedList
在链表头部和尾部插入和删除元素非常高效,时间复杂度为 O(1)。在链表中间插入和删除元素需要先找到对应的位置,时间复杂度为 O(n)。 -
访问元素:
LinkedList
不支持高效的随机访问,访问特定索引的元素需要从头(或尾)开始遍历,时间复杂度为 O(n)。 -
线程安全:
LinkedList
是非线程安全的,如果需要在多线程环境下使用,可以使用Collections.synchronizedList()
方法返回一个线程安全的LinkedList
。
总的来说,LinkedList
是一种链表结构,适合插入和删除操作频繁的场景,但在访问元素时效率较低。
- 问题 13. 介绍一下 Vector 的底层结构和相关原理
解答:Vector
是 Java 中的一种线程安全的动态数组实现,其底层是基于数组实现的。
-
存储结构:
Vector
内部使用一个数组(elementData
)来存储元素。当添加元素时,如果数组已满,就会创建一个新的更大的数组,并将原数组的内容复制到新数组中,这个过程称为扩容。 -
扩容机制:
Vector
的扩容机制是,每次扩容时,新数组的大小是原数组大小的 2 倍。如果这个值仍然不足以满足需求,那么新数组的大小就直接设置为需求的大小。 -
插入和删除:
Vector
在尾部插入和删除元素非常高效,时间复杂度为 O(1)。但是在中间或头部插入和删除元素需要移动大量元素,时间复杂度为 O(n)。 -
访问元素:由于底层是数组,所以
Vector
支持随机访问,按索引访问元素的时间复杂度为 O(1)。 -
线程安全:
Vector
的所有公共方法都进行了同步处理,所以它是线程安全的。但这也意味着在单线程环境下,Vector
的性能会比ArrayList
差一些。
总的来说,Vector
是一种线程安全的动态数组结构,适合在多线程环境下使用,但在单线程环境下,由于同步处理带来的开销,其性能会比 ArrayList
差一些。
- 问题 14. 介绍一下 Stack 的底层结构和相关原理
解答:Stack
是 Java 中的一种后进先出(LIFO)的数据结构,其底层是基于 Vector
实现的。
-
存储结构:
Stack
内部使用一个Vector
来存储元素。当添加元素(压栈)时,元素被添加到Vector
的末尾;当删除元素(弹栈)时,元素从Vector
的末尾被移除。 -
扩容机制:由于
Stack
是基于Vector
实现的,所以其扩容机制与Vector
相同。每次扩容时,新数组的大小是原数组大小的 2 倍。 -
插入和删除:
Stack
的插入和删除操作都在Vector
的末尾进行,所以非常高效,时间复杂度为 O(1)。 -
访问元素:
Stack
提供了peek()
方法来查看栈顶元素,时间复杂度为 O(1)。由于底层是Vector
,所以Stack
也支持按索引访问元素,但这并不常用。 -
线程安全:由于
Stack
是基于Vector
实现的,所以它也是线程安全的。但这也意味着在单线程环境下,Stack
的性能会比基于ArrayList
实现的Deque
差一些。
总的来说,Stack
是一种后进先出的数据结构,适合在需要后进先出操作的场景下使用,如函数调用栈、回溯算法等。
- 问题 15. 请解释一下 Java 中的 CopyOnWriteArrayList
解答:CopyOnWriteArrayList
是 Java 中的一个线程安全的 List
实现,它是通过“写时复制”(Copy-On-Write)策略来保证并发安全的。
-
写时复制策略:当对
CopyOnWriteArrayList
进行修改操作(如add
、set
、remove
等)时,它并不直接在当前数组上进行修改,而是先将当前数组进行复制,然后在新的数组上进行修改,最后再将引用指向新的数组。这样可以保证在修改过程中不会影响到读操作,实现了读写分离。 -
读操作无锁:由于所有的写操作都是在新的数组上进行的,所以读操作是无锁的,可以直接读取,这对于读多写少的场景性能提升很大。
-
写操作加锁:写操作(修改、添加、删除等)需要加锁,防止多线程同时写入时导致数据不一致。
-
内存占用:由于每次写操作都需要复制一个新的数组,所以
CopyOnWriteArrayList
在内存占用上会比普通的ArrayList
大。
总的来说,CopyOnWriteArrayList
是一种适用于读多写少且需要线程安全的场景的 List
实现。但是由于写时复制策略,它在内存占用和写操作性能上有一定的开销。
2.3、JavaQueue集合相关
- 问题 16. 简述队列和栈是什么,它们有何区别?
解答:队列(Queue)和栈(Stack)是两种常见的数据结构,它们在数据的存储和访问方式上有一些区别。
-
队列(Queue):队列是一种先进先出(FIFO,First In First Out)的数据结构,新元素添加到队列的尾部,而移除元素则从队列的头部开始。队列常用于实现需要按照元素添加顺序进行处理的场景,如任务队列、消息队列等。
-
栈(Stack):栈是一种后进先出(LIFO,Last In First Out)的数据结构,新元素添加到栈的顶部,移除元素也从栈的顶部开始。栈常用于实现需要后进先出操作的场景,如函数调用栈、撤销操作、深度优先搜索等。
总的来说,队列和栈的主要区别在于元素的访问顺序:队列是先进先出,而栈是后进先出。
- 问题 17. 请解释一下 Java 中的 Queue 和 Deque?
Queue
和 Deque
是 Java 中的两种接口,分别代表队列和双端队列这两种数据结构。
-
Queue
:队列是一种先进先出(FIFO)的数据结构,支持在队尾插入元素(offer
方法),在队头删除元素(poll
方法),查看队头元素(peek
方法)。常用的Queue
实现类有LinkedList
、PriorityQueue
等。 -
Deque
:双端队列是一种特殊的队列,它支持在两端插入和删除元素。除了支持Queue
的所有操作外,还支持在队头插入元素(offerFirst
方法),在队尾删除元素(pollLast
方法),查看队尾元素(peekLast
方法)。常用的Deque
实现类有ArrayDeque
、LinkedList
等。
在实际使用时,可以根据具体需求选择使用 Queue
还是 Deque
,以及选择合适的实现类。例如,如果需要按元素的优先级进行处理,可以使用 PriorityQueue
;如果需要在两端插入和删除元素,可以使用 Deque
。
- 问题 18. 请解释一下 Java 中的 PriorityQueue?
解答:PriorityQueue
是 Java 中的一种特殊的队列,它的特点是队列中的元素按照它们的优先级进行排序。
-
存储结构:
PriorityQueue
内部使用一个二叉小顶堆来实现。二叉小顶堆是一种特殊的二叉树,树中任意一个非叶子节点的值都不大于其子节点的值,树的根节点(顶部)是所有节点中的最小值。 -
元素排序:
PriorityQueue
中的元素可以自然排序,或者通过提供的Comparator
进行自定义排序。在添加元素时,会根据元素的优先级找到合适的位置保证堆的性质。 -
插入和删除:插入元素和删除元素(或者说是获取并删除最高优先级的元素)的时间复杂度都是 O(log n)。
-
访问元素:
PriorityQueue
提供了peek
方法来访问最高优先级的元素(堆顶元素),时间复杂度为 O(1)。 -
线程安全:
PriorityQueue
是非线程安全的,如果需要在多线程环境下使用,可以使用PriorityBlockingQueue
。
总的来说,PriorityQueue
是一种可以动态调整内部元素顺序的队列,适合实现需要动态排序的场景,如任务调度、Dijkstra 算法、Huffman 编码等。
- 问题 19. 请解释一下 Java 中的 BlockingQueue?
解答:BlockingQueue
是 Java 中的一个接口,它是一种特殊的队列,主要提供了阻塞操作的支持,适用于生产者消费者模式。
-
阻塞操作:
BlockingQueue
提供了put
和take
方法,当队列满时,put
方法会阻塞直到队列不满;当队列空时,take
方法会阻塞直到队列不空。这样可以简化生产者消费者模式的实现,无需显式地进行同步和唤醒操作。 -
非阻塞操作:
BlockingQueue
也提供了非阻塞操作offer
和poll
,如果无法立即执行操作,这些方法会返回一个特殊值(如null
或false
)而不是阻塞。 -
超时操作:
BlockingQueue
还提供了带超时的offer
和poll
方法,如果在指定的时间内无法执行操作,这些方法会返回一个特殊值。 -
实现类:
BlockingQueue
的常用实现类有ArrayBlockingQueue
、LinkedBlockingQueue
、PriorityBlockingQueue
、SynchronousQueue
等,它们分别适用于不同的场景。
总的来说,BlockingQueue
是一种支持阻塞操作的队列,适合实现生产者消费者模式,可以简化多线程编程。
2.4、JavaSet集合相关
- 问题 20. 介绍一下 HashSet 的底层结构和相关原理
解答:HashSet
是基于 HashMap
实现的,底层采用 HashMap
来保存所有元素。因此,HashSet
的数据结构就是 HashMap
的数据结构。
HashMap
是一个散列表,它存储的内容是键值对 (key-value)。HashMap
通过键的哈希值进行快速查找,具有较高的查找和插入速度。
HashSet
中的元素实际上作为 HashMap
的键存在,而 HashMap
的值则存储了一个固定的对象 PRESENT
。因此,HashSet
中的元素不能重复,这是因为 HashMap
的键不能重复。
HashSet
的操作都是基于 HashMap
的操作来实现的,例如添加元素、删除元素、查找元素等。
- 问题 21. 介绍一下 LinkedHashSet 的底层结构和相关原理
解答:LinkedHashSet
是 HashSet
的一个子类,它的底层是基于 LinkedHashMap
来实现的。
LinkedHashMap
是 HashMap
的一个子类,它在 HashMap
的基础上,增加了一个双向链表。这个双向链表连接了所有的键值对,定义了键值对的迭代顺序。迭代的顺序可以是插入顺序,也可以是访问顺序。
LinkedHashSet
中的元素实际上作为 LinkedHashMap
的键存在,而 LinkedHashMap
的值则存储了一个固定的对象 PRESENT
。因此,LinkedHashSet
中的元素不能重复,这是因为 LinkedHashMap
的键不能重复。
LinkedHashSet
的操作都是基于 LinkedHashMap
的操作来实现的,例如添加元素、删除元素、查找元素等。由于 LinkedHashSet
维护了一个运行于所有条目的双向链表,因此,可以在用迭代器遍历 LinkedHashSet
时,得到一个确定的顺序(插入的顺序)。
- 问题 22. 介绍一下 TreeSet 的底层结构和相关原理
解答:TreeSet
是基于 TreeMap
实现的,底层采用 TreeMap
来保存所有元素。因此,TreeSet
的数据结构就是 TreeMap
的数据结构。
TreeMap
是一个红黑树(自平衡的排序二叉树)。它存储的内容是键值对 (key-value)。TreeMap
通过键的自然顺序或者自定义的比较器进行排序,具有较高的查找和插入速度。
TreeSet
中的元素实际上作为 TreeMap
的键存在,而 TreeMap
的值则存储了一个固定的对象 PRESENT
。因此,TreeSet
中的元素不能重复,这是因为 TreeMap
的键不能重复。
TreeSet
的操作都是基于 TreeMap
的操作来实现的,例如添加元素、删除元素、查找元素等。由于 TreeSet
是基于 TreeMap
实现的,所以 TreeSet
的元素是有序的,元素的排序方式取决于构造 TreeSet
时提供的 Comparator
,或者依赖元素的自然顺序(Comparable
)。
TreeSet
是 SortedSet
接口的一个实现类,它提供了一个基于树结构的 Set
,元素可以按照自然顺序或者自定义的比较器进行排序。
- 问题 23. 请解释一下 Java 中的 EnumSet?
解答:EnumSet
是 Java 中的一个专门为枚举类型设计的集合类。它继承自 AbstractSet
,并实现了 Set
接口。
以下是 EnumSet
的一些特性:
-
EnumSet
中的所有元素都必须来自同一个枚举类型,它在创建时显式或隐式地指定。 -
EnumSet
是有序的,其元素的顺序就是它们在源代码中的顺序。 -
EnumSet
集合类的实现是非常高效和快速的,其大部分操作都是通过位运算实现的。 -
EnumSet
不允许使用null
元素,如果尝试添加null
元素,它会抛出NullPointerException
。 -
EnumSet
是线程不安全的,如果多个线程同时修改EnumSet
,需要进行同步处理。
以下是创建 EnumSet
的一些方法:
EnumSet.allOf(Class<E> elementType)
:创建一个包含指定枚举类型的所有元素的EnumSet
。EnumSet.noneOf(Class<E> elementType)
:创建一个指定枚举类型的空EnumSet
。EnumSet.of(E first, E... rest)
:创建一个最初包含指定元素的EnumSet
。EnumSet.range(E from, E to)
:创建一个包含从from
元素到to
元素范围内的所有元素的EnumSet
。EnumSet.copyOf(Collection<E> c)
或EnumSet.copyOf(EnumSet<E> s)
:创建一个与指定EnumSet
具有相同元素类型的EnumSet
,最初包含相同的元素(如果有的话)。
- 问题 24. 请解释一下 Java 中的 SortedSet
解答:SortedSet
是 Java 集合框架中的一个接口,它继承自 Set
接口。SortedSet
接口为集合中的元素提供了一个总的排序。
以下是 SortedSet
的一些特性:
-
SortedSet
中的元素按照自然顺序或者自定义的比较器(Comparator)进行排序。 -
SortedSet
不允许插入null
元素。如果尝试插入null
元素,它会抛出NullPointerException
。 -
SortedSet
是线程不安全的,如果多个线程同时修改SortedSet
,需要进行同步处理。
以下是 SortedSet
的一些主要方法:
-
Comparator<? super E> comparator()
:返回用于对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回null
。 -
E first()
:返回此 set 中当前第一个(最低)元素。 -
SortedSet<E> headSet(E toElement)
:返回此 set 的部分视图,其元素严格小于toElement
。 -
E last()
:返回此 set 中当前最后一个(最高)元素。 -
SortedSet<E> subSet(E fromElement, E toElement)
:返回此 set 的部分视图,其元素的范围从fromElement
(包括)到toElement
(不包括)。 -
SortedSet<E> tailSet(E fromElement)
:返回此 set 的部分视图,其元素大于等于fromElement
。
- 问题 25. 请解释一下 Java 中的 NavigableSet
解答:NavigableSet
是 Java 集合框架中的一个接口,它继承自 SortedSet
接口。NavigableSet
描述了一种可以通过搜索方法导航的数据结构。
以下是 NavigableSet
的一些特性:
-
NavigableSet
中的元素按照自然顺序或者自定义的比较器(Comparator)进行排序。 -
NavigableSet
提供了多种导航方法,例如获取小于/大于某个元素的最大/最小元素等。 -
NavigableSet
不允许插入null
元素。如果尝试插入null
元素,它会抛出NullPointerException
。 -
NavigableSet
是线程不安全的,如果多个线程同时修改NavigableSet
,需要进行同步处理。
以下是 NavigableSet
的一些主要方法:
-
E lower(E e)
:返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回null
。 -
E floor(E e)
:返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回null
。 -
E ceiling(E e)
:返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回null
。 -
E higher(E e)
:返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回null
。 -
E pollFirst()
:获取并移除此 set 中的第一个(最低)元素;如果此 set 为空,则返回null
。 -
E pollLast()
:获取并移除此 set 中的最后一个(最高)元素;如果此 set 为空,则返回null
。
TreeSet
是 NavigableSet
接口的一个实现类,它提供了一个基于树结构的 Set
,元素可以按照自然顺序或者自定义的比较器进行排序。