- 全网最全的 Java 面试题内容梳理(持续更新中)
- Java基础面试题&知识点总结(上篇)
- Java基础面试题&知识点总结(下篇)
文章目录
1、Java基础面试题问题
- 问题 21. 介绍一下 Set 集合,以及它有怎样的特性?
- 问题 22. 介绍一下 Set 集合,有哪些常见的方法?
- 问题 23. 介绍一下 HashSet 的底层结构和相关原理
- 问题 24. 介绍一下 LinkedHashSet 的底层结构和相关原理
- 问题 25. 介绍一下 TreeSet 的底层结构和相关原理
- 问题 26. 请解释一下 Java 中的 EnumSet?
- 问题 27. 请解释一下 Java 中的 SortedSet?
- 问题 28. 请解释一下 Java 中的 NavigableSet
- 问题 29. 请解释一下 Java 中的 Comparable 接口?
- 问题 30. 请解释一下 Java 中的 Comparator 接口?
- 问题 31. 请解释一下 Java 中的 CopyOnWrite
- 问题 32. 请解释一下 Java 中的 CopyOnWriteArrayList
- 问题 33. 简述什么是 Fail Fast?
- 问题 34. 简述什么是 Fail Safe?
- 问题 35. 请解释一下 Java 中的 ConcurrentModificationException?
- 问题 36. Java 中迭代器 Iterator 是什么?
- 问题 37. Java 中 Iterator 和 ListIterator 有什么区别?
- 问题 38. 为什么使用 Iterator 删除元素更加安全?
- 问题 39. 如何在 Java 中使用 Java 8 的 Stream API 处理集合?
- 问题 40. 如何在 Java 中使用 Java 8 的 forEach 方法遍历集合?
2、Java基础面试题解答
2.1、JavaSet集合相关-特性&方法
- 问题 21. 介绍一下 Set 集合,以及它有怎样的特性?
解答:Set 是 Java 集合框架中的一个接口,它继承自 Collection 接口。Set 集合中的元素是无序的,并且不包含重复的元素。
Set 集合的主要特性包括:
-
无序:
Set集合中的元素没有特定的顺序。也就是说,我们不能通过索引来访问Set集合中的元素。 -
不可重复:
Set集合不允许插入重复的元素。如果试图插入已经存在的元素,Set集合不会报错,但是插入操作不会有任何效果。 -
元素可为 null:
Set集合中可以添加 null 元素。
Java 中的 HashSet、LinkedHashSet 和 TreeSet 都是 Set 接口的实现类,它们具有上述的 Set 特性,但是在内部实现和性能上有所不同。例如,HashSet 是基于哈希表实现的,插入和查询的性能较高;LinkedHashSet 是在 HashSet 的基础上,增加了链表来保证元素的插入顺序;TreeSet 是基于红黑树实现的,元素会按照自然顺序或者自定义的顺序进行排序。
- 问题 22. 介绍一下 Set 集合,有哪些常见的方法?
解答:Set 接口在 Collection 接口的基础上,没有新增任何方法,主要的方法都继承自 Collection 接口。以下是 Set 接口中一些常见的方法:
-
boolean add(E e):向集合中添加元素,如果集合已经包含该元素,则返回false。 -
void clear():清空集合,移除所有元素。 -
boolean contains(Object o):判断集合是否包含指定的元素。 -
boolean isEmpty():判断集合是否为空。 -
Iterator<E> iterator():返回一个用于遍历集合的迭代器。 -
boolean remove(Object o):从集合中移除指定的元素。 -
int size():返回集合中元素的数量。 -
Object[] toArray():将集合转换为数组。
以上就是 Set 接口中一些常见的方法,它们提供了丰富的功能,使得我们可以方便地对集合进行操作。
2.2、JavaSet集合相关-具体实现
- 问题 23. 介绍一下 HashSet 的底层结构和相关原理
解答:HashSet 是基于 HashMap 实现的,底层采用 HashMap 来保存所有元素。因此,HashSet 的数据结构就是 HashMap 的数据结构。
HashMap 是一个散列表,它存储的内容是键值对 (key-value)。HashMap 通过键的哈希值进行快速查找,具有较高的查找和插入速度。
HashSet 中的元素实际上作为 HashMap 的键存在,而 HashMap 的值则存储了一个固定的对象 PRESENT。因此,HashSet 中的元素不能重复,这是因为 HashMap 的键不能重复。
HashSet 的操作都是基于 HashMap 的操作来实现的,例如添加元素、删除元素、查找元素等。
- 问题 24. 介绍一下 LinkedHashSet 的底层结构和相关原理
解答:LinkedHashSet 是 HashSet 的一个子类,它的底层是基于 LinkedHashMap 来实现的。
LinkedHashMap 是 HashMap 的一个子类,它在 HashMap 的基础上,增加了一个双向链表。这个双向链表连接了所有的键值对,定义了键值对的迭代顺序。迭代的顺序可以是插入顺序,也可以是访问顺序。
LinkedHashSet 中的元素实际上作为 LinkedHashMap 的键存在,而 LinkedHashMap 的值则存储了一个固定的对象 PRESENT。因此,LinkedHashSet 中的元素不能重复,这是因为 LinkedHashMap 的键不能重复。
LinkedHashSet 的操作都是基于 LinkedHashMap 的操作来实现的,例如添加元素、删除元素、查找元素等。由于 LinkedHashSet 维护了一个运行于所有条目的双向链表,因此,可以在用迭代器遍历 LinkedHashSet 时,得到一个确定的顺序(插入的顺序)。
- 问题 25. 介绍一下 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,元素可以按照自然顺序或者自定义的比较器进行排序。
- 问题 26. 请解释一下 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,最初包含相同的元素(如果有的话)。
- 问题 27. 请解释一下 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。
- 问题 28. 请解释一下 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,元素可以按照自然顺序或者自定义的比较器进行排序。
2.3、Java排序接口相关
- 问题 29. 请解释一下 Java 中的 Comparable 接口?
解答:Comparable 是 Java 中的一个接口,用于定义对象之间的自然排序规则。如果一个类实现了 Comparable 接口,那么它的对象就可以进行比较和排序。
Comparable 接口定义了一个 compareTo 方法,需要实现类进行重写。compareTo 方法接收一个同类型的对象作为参数,返回一个整数,有三种可能:
- 返回 0,表示 this 等于参数对象;
- 返回正数,表示 this 大于参数对象;
- 返回负数,表示 this 小于参数对象。
例如,下面的代码定义了一个 Person 类,实现了 Comparable 接口,按照年龄进行排序:
public class Person implements Comparable<Person> {
private String name;
private int age;
// ... 省略构造方法和 getter、setter 方法 ...
@Override
public int compareTo(Person other) {
return this.age - other.age;
}
}
这样,我们就可以对 Person 对象进行排序:
List<Person> people = Arrays.asList(
new Person("Alice", 20),
new Person("Bob", 18),
new Person("Charlie", 22)
);
Collections.sort(people);
以上就是 Comparable 接口的基本概念和用法。通过实现 Comparable 接口,我们可以定义对象的自然排序规则,使得对象可以进行比较和排序。
- 问题 30. 请解释一下 Java 中的 Comparator 接口?
解答:Comparator 是 Java 中的一个接口,用于定义对象之间的定制排序规则。如果一个类没有实现 Comparable 接口,或者实现了但是开发者希望有其他的排序方式,那么可以使用 Comparator。
Comparator 接口定义了一个 compare 方法,需要开发者进行重写。compare 方法接收两个同类型的对象作为参数,返回一个整数,有三种可能:
- 返回 0,表示第一个参数等于第二个参数;
- 返回正数,表示第一个参数大于第二个参数;
- 返回负数,表示第一个参数小于第二个参数。
例如,下面的代码定义了一个 Person 类,以及一个按照年龄排序的 Comparator:
public class Person {
private String name;
private int age;
// ... 省略构造方法和 getter、setter 方法 ...
}
public class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
这样,我们就可以使用 AgeComparator 对 Person 对象进行排序:
List<Person> people = Arrays.asList(
new Person("Alice", 20),
new Person("Bob", 18),
new Person("Charlie", 22)
);
Collections.sort(people, new AgeComparator());
以上就是 Comparator 接口的基本概念和用法。通过实现 Comparator 接口,我们可以定义对象的定制排序规则,使得对象可以按照我们想要的方式进行排序。
2.4、Java集合并发相关
- 问题 31. 请解释一下 Java 中的 CopyOnWrite
解答:CopyOnWrite 是 Java 中的一种并发策略,主要应用于多线程环境下的读多写少的场景。Java 提供了 CopyOnWriteArrayList 和 CopyOnWriteArraySet 两个线程安全的集合类,它们的实现都采用了 “写时复制”(Copy-On-Write)的策略。
“写时复制” 的基本思想是:当我们需要修改集合(如添加、删除元素)时,不直接在当前集合上进行修改,而是先将当前集合进行复制,然后在新的副本上进行修改,最后再将引用指向新的副本。这样,读操作都是在原集合上进行,不需要加锁;写操作是在副本上进行,也不会影响读操作,实现了读写分离。
“写时复制” 的优点是可以实现高并发的读操作,适合读多写少的并发场景。但是,它也有一些缺点:
-
内存占用:每次写操作都会复制一份新的集合,如果数据量大,会占用较多的内存。
-
数据一致性:读操作可能无法读取到最新的数据,因为写操作是在副本上进行的。
-
写操作性能:由于需要复制新的集合,所以写操作的性能会比较低。
以上就是 “写时复制” 的基本原理和特点。在使用 CopyOnWriteArrayList 和 CopyOnWriteArraySet 时,需要根据实际的并发场景来权衡其优缺点。
- 问题 32. 请解释一下 Java 中的 CopyOnWriteArrayList
解答:CopyOnWriteArrayList 是 Java 中的一个线程安全的 List 实现,它是通过"写时复制"(Copy-On-Write)策略来保证并发安全的。
-
写时复制策略:当对
CopyOnWriteArrayList进行修改操作(如add、set、remove等)时,它并不直接在当前数组上进行修改,而是先将当前数组进行复制,然后在新的数组上进行修改,最后再将引用指向新的数组。这样可以保证在修改过程中不会影响到读操作,实现了读写分离。 -
读操作无锁:由于所有的写操作都是在新的数组上进行的,所以读操作是无锁的,可以直接读取,这对于读多写少的场景性能提升很大。
-
写操作加锁:写操作(修改、添加、删除等)需要加锁,防止多线程同时写入时导致数据不一致。
-
内存占用:由于每次写操作都需要复制一个新的数组,所以
CopyOnWriteArrayList在内存占用上会比普通的ArrayList大。
总的来说,CopyOnWriteArrayList 是一种适用于读多写少且需要线程安全的场景的 List 实现。但是由于写时复制策略,它在内存占用和写操作性能上有一定的开销。
- 问题 33. 简述什么是 Fail Fast?
解答:“Fail Fast” 是 Java 集合框架中的一个错误检测机制。当多个线程对一个集合进行并发操作时,如果一个线程通过迭代器(Iterator)在遍历集合的过程中,其他线程修改了集合的结构(如添加、删除元素),那么正在遍历的线程会立即抛出 ConcurrentModificationException 异常。
“Fail Fast” 的主要目的是为了快速发现并发修改的问题,而不是等到程序运行一段时间后才发现问题。这种机制可以帮助我们尽早发现并发编程中的错误,避免出现难以预料的结果。
需要注意的是,“Fail Fast” 机制并不能保证在所有情况下都能检测到并发修改的问题,它只能尽最大可能地发现问题。另外,“Fail Fast” 机制并不是用来解决并发问题的,如果需要在多线程环境下安全地操作集合,应该使用线程安全的集合类,或者通过同步机制来保护非线程安全的集合。
- 问题 34. 简述什么是 Fail Safe?
“Fail Safe” 是 Java 集合框架中的一种错误处理机制。在 “Fail Safe” 机制下,当一个线程正在遍历集合的过程中,其他线程对集合进行修改,不会抛出 ConcurrentModificationException 异常。
“Fail Safe” 机制的实现通常是通过创建集合的副本来实现的。当进行遍历操作时,遍历的是原集合的副本,而不是原集合。因此,对原集合的修改不会影响到遍历操作,也就不会抛出 ConcurrentModificationException 异常。
Java 中的 CopyOnWriteArrayList 和 CopyOnWriteArraySet 就是使用了 “Fail Safe” 机制。这两个类在进行修改操作时,会创建原集合的副本,然后在副本上进行修改,最后再将引用指向新的副本。
需要注意的是,“Fail Safe” 机制虽然可以避免抛出 ConcurrentModificationException 异常,但是由于需要创建集合的副本,所以在内存占用和性能上会有一些开销。另外,由于遍历操作是在原集合的副本上进行的,所以可能无法看到其他线程对原集合的修改结果。
- 问题 35. 请解释一下 Java 中的 ConcurrentModificationException?
解答:ConcurrentModificationException 是 Java 中的一个运行时异常,通常在多线程环境下,一个线程正在遍历集合的过程中,另一个线程修改了集合的结构(如添加、删除元素),那么正在遍历的线程可能会抛出这个异常。
这个异常通常是由 “Fail Fast” 机制引发的。“Fail Fast” 是 Java 集合框架中的一个错误检测机制,它的目的是为了尽早发现并发修改的问题,避免出现难以预料的结果。
需要注意的是,“Fail Fast” 机制并不能保证在所有情况下都能检测到并发修改的问题,它只能尽最大可能地发现问题。另外,“Fail Fast” 机制并不是用来解决并发问题的,如果需要在多线程环境下安全地操作集合,应该使用线程安全的集合类,或者通过同步机制来保护非线程安全的集合。
如果遇到 ConcurrentModificationException 异常,应该检查代码,确保在遍历集合的过程中,没有其他线程对集合进行修改。如果需要在遍历过程中修改集合,可以使用 Iterator 的 remove() 方法,或者使用 ListIterator 的 add() 和 set() 方法,这些方法可以安全地在遍历过程中修改集合。
2.5、Java迭代器相关
2.2、Java迭代器相关
- 问题 36. Java 中迭代器 Iterator 是什么?
Iterator 是 Java 中的一个接口,它提供了一种统一的方式来遍历集合中的元素。Iterator 接口定义了三个方法:
-
hasNext():检查是否还有下一个元素,如果有则返回true,否则返回false。 -
next():返回当前元素,并将迭代器向前移动到下一个元素。 -
remove():删除迭代器最后一次返回的元素。这个方法是可选的,不是所有的迭代器都支持。
在 Java 的集合框架中,所有的 Collection 子类都提供了一个 iterator() 方法,用于返回一个 Iterator 对象,通过这个对象可以遍历集合中的元素。
例如,下面的代码展示了如何使用 Iterator 遍历一个 ArrayList:
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
这就是 Iterator 的基本概念和用法。通过 Iterator,我们可以方便地遍历集合中的元素,而不需要关心集合的具体实现。
- 问题 37. Java 中 Iterator 和 ListIterator 有什么区别?
Iterator 和 ListIterator 都是 Java 中的迭代器接口,它们都提供了遍历集合元素的方法,但是 ListIterator 提供了更多的功能。
-
双向遍历:
Iterator只能进行单向遍历,从前往后。而ListIterator支持双向遍历,既可以从前往后,也可以从后往前。ListIterator提供了hasPrevious和previous方法来实现从后往前的遍历。 -
添加元素:
ListIterator提供了add方法,可以在遍历过程中添加元素,而Iterator不支持这个操作。 -
修改元素:
ListIterator提供了set方法,可以修改最后一次返回的元素,而Iterator不支持这个操作。 -
获取元素索引:
ListIterator提供了nextIndex和previousIndex方法,可以获取下一个或上一个元素的索引,而Iterator不支持这个操作。 -
使用范围:
Iterator可以应用于所有的Collection子类,而ListIterator只能应用于List子类。
以上就是 Iterator 和 ListIterator 的主要区别。在需要进行更复杂的遍历操作时,可以选择使用 ListIterator。
- 问题 38. 为什么使用 Iterator 删除元素更加安全?
使用 Iterator 删除集合中的元素更加安全,主要有以下两个原因:
-
避免并发修改异常:在使用
for-each循环或者普通的for循环遍历集合的过程中,如果直接调用集合的remove方法删除元素,可能会抛出ConcurrentModificationException异常。这是因为在遍历过程中,集合的结构发生了改变,但是这个改变并没有同步到正在进行的迭代过程中,所以会抛出异常。 -
避免索引问题:在使用普通的
for循环遍历List的过程中,如果直接调用List的remove方法删除元素,可能会出现索引问题。因为删除元素后,后面的元素的索引会发生改变,可能会导致跳过某些元素或者重复处理某些元素。而使用Iterator的remove方法删除元素,迭代器会正确地移动到下一个元素,不会出现这个问题。
因此,推荐在遍历集合的过程中,使用 Iterator 的 remove 方法删除元素。
2.6、Java-8中的流处理
- 问题 39. 如何在 Java 中使用 Java 8 的 Stream API 处理集合?
Java 8 引入了一个新的 Stream API,它提供了一种新的方式来处理集合。Stream API 可以让我们以声明式的方式处理数据,使代码更简洁,易读。
以下是一些使用 Stream API 处理集合的例子:
-
过滤:使用
filter()方法可以过滤出满足条件的元素。List<String> names = Arrays.asList("John", "Jane", "Adam", "Tom"); List<String> result = names.stream() .filter(name -> name.startsWith("J")) .collect(Collectors.toList());上述代码会过滤出所有以 “J” 开头的名字。
-
映射:使用
map()方法可以将元素转换成其他形式。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> squares = numbers.stream() .map(n -> n * n) .collect(Collectors.toList());上述代码会将每个数字映射成它的平方。
-
排序:使用
sorted()方法可以对元素进行排序。List<String> names = Arrays.asList("John", "Jane", "Adam", "Tom"); List<String> sortedNames = names.stream() .sorted() .collect(Collectors.toList());上述代码会将名字按照字母顺序进行排序。
-
统计:使用
count()、max()、min()、average()等方法可以进行统计。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); long count = numbers.stream().count(); Optional<Integer> max = numbers.stream().max(Integer::compare);上述代码会统计数字的数量和最大值。
以上就是一些使用 Stream API 处理集合的例子,Stream API 还提供了很多其他的方法,如 reduce()、collect()、flatMap() 等,可以满足各种复杂的数据处理需求。
- 问题 40. 如何在 Java 中使用 Java 8 的 forEach 方法遍历集合?
解答:Java 8 在 Iterable 接口中添加了一个新的 forEach 方法,可以更简洁地遍历集合。forEach 方法接受一个 Consumer 函数式接口的实例作为参数,用于处理集合中的每个元素。
以下是使用 forEach 方法遍历集合的例子:
List<String> names = Arrays.asList("John", "Jane", "Adam", "Tom");
// 使用 lambda 表达式
names.forEach(name -> System.out.println(name));
// 使用方法引用
names.forEach(System.out::println);
在上述代码中,我们使用了 lambda 表达式和方法引用两种方式来处理集合中的每个元素。这两种方式都可以使代码更简洁,易读。
需要注意的是,forEach 方法的遍历顺序并不是固定的,它取决于具体的集合实现。如果需要固定的遍历顺序,应该使用 List 或者 LinkedHashSet 等有序的集合。










