当前位置: 首页 > news >正文

Java面试题总结

一、Java集合框架是什么?说出一些集合框架的优点?

每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector、Stack、HashTable和Array。随着集合的广泛使用,Java 1.2提出了囊括所有集合接口、实现和算法的集合框架。在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久。它还包括在Java并发包中,阻塞接口以及它们的实现。
集合框架的部分优点如下:

  1. 降低开发成本:使用核心集合类降低开发成本,而非实现我们自己的集合类。
  2. 提高代码质量:随着使用经过严格测试的集合框架类,代码质量会得到提高。
  3. 降低维护成本:通过使用JDK附带的集合类,可以降低代码维护成本。
  4. 复用性和可操作性 。

二、集合框架中的泛型有什么优点?

Java 1.5引入了泛型,所有的集合接口和实现都大量地使用它。泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。

三、Java集合框架的基础接口有哪些?

  1. Collection:为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java平台不提供这个接口任何直接的实现。
  2. Set:是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。
  3. List:是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List更像长度动态变换的数组。
  4. Map:是一个将key映射到value的对象.一个Map不能包含重复的key:每个key最多只能映射一个value。
  5. 一些其它的接口有Queue、Dequeue、SortedSet、SortedMap和ListIterator。

四、为何Collection不从Cloneable和Serializable接口继承?

Collection接口指定一组对象,对象即为它的元素。如何维护这些元素由Collection的具体实现决定。例如,一些如List的Collection实现允许重复的元素,而其它的如Set就不允许。很多Collection实现有一个公有的clone方法。然而,把它放到集合的所有实现中也是没有意义的。这是因为Collection是一个抽象表现。重要的是实现。

当与具体实现打交道的时候,克隆或序列化的语义和含义才发挥作用。所以,具体实现应该决定如何对它进行克隆或序列化,或它是否可以被克隆或序列化。

在所有的实现中授权克隆和序列化,最终导致更少的灵活性和更多的限制。特定的实现应该决定它是否可以被克隆和序列化。

五、为何Map接口不继承Collection接口?

尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。

如果Map继承Collection接口,那么元素去哪儿?Map包含key - value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。

六、Iterator是什么?

Iterator接口提供遍历任何Collection的接口。我们可以从一个Collection中使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素。

七、Enumeration和Iterator接口的区别?

  1. 速度和内存:Enumeration的速度是Iterator的两倍,也使用更少的内存。Enumeration是非常基础的,也满足了基础的需要。
  2. 安全性:与Enumeration相比,Iterator更加安全,因为当一个集合正在被遍历的时候,它会阻止其它线程去修改集合。
  3. 移除元素:迭代器允许调用者从集合中移除元素,而Enumeration不能做到。为了使它的功能更加清晰,迭代器方法名已经经过改善。

八、为何没有像Iterator.add()这样的方法,向集合中添加元素?

语义不明,已知的是,Iterator的协议不能确保迭代的次序。然而要注意,ListIterator没有提供一个add操作,它要确保迭代的顺序。

九、为何迭代器没有一个方法可以直接获取下一个元素,而不需要移动游标?

它可以在当前Iterator的顶层实现,但是它用得很少,如果将它加到接口中,每个继承都要去实现它,这没有意义。

十、Iterater和ListIterator之间有什么区别?

  1. 遍历范围:我们可以使用Iterator来遍历Set和List集合,而ListIterator只能遍历List。
  2. 遍历方向:Iterator只可以向前遍历,而LIstIterator可以双向遍历。
  3. 额外功能:ListIterator从Iterator接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

十一、遍历一个List有哪些不同的方式?

List<String> strList = new ArrayList<>();
//使用for - each循环
for(String obj : strList){System.out.println(obj);
}
//using iterator
Iterator<String> it = strList.iterator();
while(it.hasNext()){String obj = it.next();System.out.println(obj);
}
 
 

使用迭代器更加线程安全,因为它可以确保,在当前遍历的集合元素被更改的时候,它会抛出ConcurrentModificationException。

十二、通过迭代器fail - fast属性,你明白了什么?

每次我们尝试获取下一个元素的时候,Iterator fail - fast属性检查当前集合结构里的任何改动。如果发现任何改动,它抛出ConcurrentModificationException。Collection中所有Iterator的实现都是按fail - fast来设计的(ConcurrentHashMap和CopyOnWriteArrayList这类并发集合类除外)。

十三、fail - fast与fail - safe有什么区别?

  1. 作用方式:Iterator的fail - fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。Java.util包中的所有集合类都被设计为fail - fast的,而java.util.concurrent中的集合类都为fail - safe的。
  2. 异常抛出:Fail - fast迭代器抛出ConcurrentModificationException,而fail - safe迭代器从不抛出ConcurrentModificationException。

十四、在迭代一个集合的时候,如何避免ConcurrentModificationException?

  1. 同步操作:在遍历一个集合的时候,我们可以使用并发集合类来避免ConcurrentModificationException。例如使用CopyOnWriteArrayList来替换ArrayList。
  2. 使用迭代器的remove方法:边遍历边修改Collection的唯一正确方式是使用Iterator.remove()方法。

十五、怎么确保一个集合不能被修改?

可以使用Collections.unmodifiableCollection(Collection c)方法来创建一个只读集合,这样改变集合的任何操作都会抛出Java.lang.UnsupportedOperationException异常。
示例代码如下:

List list = new ArrayList<>();
list.add("x");
Collection clist = Collections.unmodifiableCollection(list);
clist.add("y"); // 运行时此行报错
System.out.println(list.size());
 
 

十六、ArrayList和LinkedList的区别是什么?

  1. 底层数据结构:ArrayList是基于数组实现的,LinkedList是基于双向链表实现的。
  2. 随机访问性能:ArrayList支持快速随机访问,时间复杂度为O(1),因为它可以通过数组索引直接访问元素。而LinkedList的随机访问性能较差,时间复杂度为O(n),因为它需要遍历链表来找到指定位置的元素。
  3. 插入和删除性能:在ArrayList中,插入和删除元素可能需要移动大量元素,尤其是在数组中间位置进行操作时,时间复杂度为O(n)。而LinkedList的插入和删除操作只需要修改相邻节点的引用,时间复杂度为O(1),但如果要找到指定位置的节点,仍然需要O(n)的时间。
  4. 内存占用:ArrayList的内存占用相对较小,因为它只需要存储元素本身。而LinkedList每个节点除了存储元素外,还需要存储两个引用(指向前一个和后一个节点),因此内存占用较大。

十七、HashMap和HashTable的区别是什么?

  1. 线程安全性:HashMap是非线程安全的,HashTable是线程安全的,HashTable使用了synchronized关键字来保证线程安全。
  2. null键值对:HashMap的key和value都允许为null,但是HashTable的key和value都不允许为null。
  3. 性能:由于HashTable是线程安全的,所以在单线程环境下,HashMap的性能要高于HashTable。
  4. 初始容量和扩容机制:HashMap的初始容量为16,扩容每次都是2的n次幂。HashTable的初始容量为11,扩容是原来的2倍加1。

十八、HashSet和TreeSet的区别是什么?

  1. 底层数据结构:HashSet基于HashMap实现,使用哈希表存储元素。TreeSet基于红黑树实现,元素按照自然顺序或自定义顺序排序。
  2. 元素顺序:HashSet中的元素是无序的,插入顺序和存储顺序不一定相同。而TreeSet中的元素是有序的,可以按照自然顺序(如数字从小到大、字符串按字典序)或通过实现Comparator接口来定义自定义顺序。
  3. 添加和查询性能:HashSet的添加和查询操作平均时间复杂度为O(1),因为它使用哈希表进行快速查找。TreeSet的添加和查询操作时间复杂度为O(log n),因为红黑树的高度是对数级别的。
  4. 唯一性保证:HashSet通过hashCode()和equals()方法来保证元素的唯一性。TreeSet通过比较元素的顺序来保证唯一性,如果两个元素比较相等,则不会同时存在于TreeSet中。

十九、ConcurrentHashMap的实现原理是什么?

  1. JDK 1.7:在JDK 1.7中,ConcurrentHashMap采用分段锁机制,它将数据分成多个段(Segment),每个段都有自己的锁。当一个线程访问某个段时,不会影响其他段的访问,从而提高了并发性能。每个Segment类似于一个小型的HashMap,包含一个数组和链表。
  2. JDK 1.8:在JDK 1.8中,ConcurrentHashMap摒弃了分段锁机制,采用CAS(Compare and Swap)操作和Synchronized关键字相结合的方式。它的底层数据结构和HashMap类似,也是数组 + 链表 + 红黑树。在进行插入、删除、查询等操作时,首先通过CAS操作尝试更新数据,如果失败,则使用Synchronized关键字对节点进行加锁,以保证线程安全。

二十、如何遍历HashMap和HashTable?

遍历HashMap

HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("one", 1);
hashMap.put("two", 2);
// 使用entrySet遍历
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {System.out.println(entry.getKey() + " : " + entry.getValue());
}
// 使用keySet遍历
for (String key : hashMap.keySet()) {System.out.println(key + " : " + hashMap.get(key));
}
 
 

遍历HashTable

Hashtable<String, Integer> hashtable = new Hashtable<>();
hashtable.put("one", 1);
hashtable.put("two", 2);
// 使用entrySet遍历
for (Map.Entry<String, Integer> entry : hashtable.entrySet()) {System.out.println(entry.getKey() + " : " + entry.getValue());
}
// 使用keySet遍历
for (String key : hashtable.keySet()) {System.out.println(key + " : " + hashtable.get(key));
}
 
 

二十一、如何实现一个线程安全的List?

  1. 使用Vector:Vector是Java早期提供的一个线程安全的List实现,它的方法都使用了synchronized关键字进行同步。但是由于同步机制的开销,性能相对较低。
  2. 使用Collections.synchronizedList():可以通过Collections类的synchronizedList()方法将一个普通的List转换为线程安全的List。
List<String> list = new ArrayList<>();
List<String> synchronizedList = Collections.synchronizedList(list);
 
 
  1. 使用CopyOnWriteArrayList:CopyOnWriteArrayList是Java并发包提供的一个线程安全的List实现。它的原理是在进行写操作(如添加、删除元素)时,会先复制一个新的数组,在新数组上进行操作,操作完成后再将原数组引用指向新数组。读操作则直接读取原数组,这样读操作和写操作可以并发进行,并且读操作不会受到写操作的影响。

二十二、如何实现一个线程安全的Set?

  1. 使用Collections.synchronizedSet():通过Collections类的synchronizedSet()方法将一个普通的Set转换为线程安全的Set。
Set<String> set = new HashSet<>();
Set<String> synchronizedSet = Collections.synchronizedSet(set);
 
 
  1. 使用CopyOnWriteArraySet:CopyOnWriteArraySet是Java并发包提供的一个线程安全的Set实现,它内部是基于CopyOnWriteArrayList实现的。它的特点和CopyOnWriteArrayList类似,写操作时复制数组,读操作直接读取原数组,保证了线程安全。

二十三、如何实现一个线程安全的Map?

  1. 使用HashTable:HashTable是一个线程安全的Map实现,它的方法都使用了synchronized关键字进行同步,但是性能较低。
  2. 使用Collections.synchronizedMap():通过Collections类的synchronizedMap()方法将一个普通的Map转换为线程安全的Map。
Map<String, Integer> map = new HashMap<>();
Map<String, Integer> synchronizedMap = Collections.synchronizedMap(map);
 
 
  1. 使用ConcurrentHashMap:ConcurrentHashMap是Java并发包提供的高效线程安全的Map实现,在JDK 1.7中采用分段锁机制,JDK 1.8中采用CAS和Synchronized相结合的方式,具有较高的并发性能。

二十四、如何在Java中实现LRU缓存?

LRU(Least Recently Used)缓存即最近最少使用缓存。可以使用LinkedHashMap来实现LRU缓存。LinkedHashMap维护了一个双向链表,记录了元素的访问顺序。

import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K, V> extends LinkedHashMap<K, V> {private final int capacity;public LRUCache(int capacity) {super(capacity, 0.75f, true);this.capacity = capacity;}@Overrideprotected boolean removeEldestEntry(Map.Entry<K, V> eldest) {return size() > capacity;}
}
 
 

在上述代码中,通过继承LinkedHashMap并重写removeEldestEntry方法,当缓存大小超过设定的capacity时,自动移除最久未使用的元素。

二十五、如何在Java中实现FIFO队列?

可以使用Queue接口的实现类,如ArrayDeque或LinkedList来实现FIFO(先进先出)队列。

使用ArrayDeque实现FIFO队列

import java.util.ArrayDeque;
import java.util.Queue;
public class FIFOQueueExample {public static void main(String[] args) {Queue<Integer> queue = new ArrayDeque<>();queue.add(1);queue.add(2);queue.add(3);while (!queue.isEmpty()) {System.out.println(queue.poll());}}
}
 
 

使用LinkedList实现FIFO队列

import java.util.LinkedList;
import java.util.Queue;
public class FIFOQueueExample {public static void main(String[] args) {Queue<Integer> queue = new LinkedList<>();queue.add(1);queue.add(2);queue.add(3);while (!queue.isEmpty()) {System.out.println(queue.poll());}}
}
 
 

在这两个例子中,add方法用于向队列中添加元素,poll方法用于从队列头部取出元素,从而实现了FIFO的特性。

二十六、如何在Java中实现优先级队列?

可以使用PriorityQueue类来实现优先级队列。PriorityQueue是一个基于堆数据结构的无界优先级队列,默认情况下,元素按照自然顺序进行排序,也可以通过传入自定义的Comparator来实现自定义排序。

import java.util.PriorityQueue;
public class PriorityQueueExample {public static void main(String[] args) {PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();priorityQueue.add(3);priorityQueue.add(1);priorityQueue.add(2);while (!priorityQueue.isEmpty()) {System.out.println(priorityQueue.poll());}}
}
 
 

在上述示例中,元素按照从小到大的顺序被取出。如果需要自定义排序,可以创建一个实现Comparator接口的类,并在创建PriorityQueue时传入该类的实例。

二十七、如何在Java中实现栈?

在 Java 里实现栈有两种比较常见的方法:一是借助 Java 集合框架中的Stack类,二是利用Deque接口的实现类,像ArrayDeque。下面为你详细介绍这两种实现方式。

方法一:使用 Java 内置的Stack类
Java 提供了java.util.Stack类,它继承自Vector,能用来实现栈的基本功能。下面是一个简单的示例:

```java
import java.util.Stack;

public class StackExample {
public static void main(String[] args) {
// 创建一个栈对象
Stack stack = new Stack<>();

    // 入栈操作stack.push("Java");stack.push("Python");stack.push("C++");// 出栈操作System.out.println("出栈元素: " + stack.pop()); // 输出: C++// 获取栈顶元素System.out.println("栈顶元素: " + stack.peek()); // 输出: Python// 判断栈是否为空System.out.println("栈是否为空: " + stack.isEmpty()); // 输出: false// 获取栈的大小System.out.println("栈的大小: " + stack.size()); // 输出: 2
}
 
 

}
...

方法二:使用Deque接口(推荐做法)
虽然Stack类使用起来很方便,但由于它是线程安全的,在单线程环境下会带来一些性能损耗。所以,更推荐使用Deque接口的实现类,例如ArrayDeque。

```java
import java.util.Deque;
import java.util.ArrayDeque;

public class DequeStackExample {
public static void main(String[] args) {
// 创建一个使用ArrayDeque实现的栈
Deque stack = new ArrayDeque<>();

    // 入栈操作stack.push("Apple");stack.push("Banana");stack.push("Cherry");// 出栈操作System.out.println("出栈元素: " + stack.pop()); // 输出: Cherry// 获取栈顶元素System.out.println("栈顶元素: " + stack.peek()); // 输出: Banana// 判断栈是否为空System.out.println("栈是否为空: " + stack.isEmpty()); // 输出: false// 获取栈的大小System.out.println("栈的大小: " + stack.size()); // 输出: 2
}
 
 

}
...

方法三:自定义栈类(基于数组)
你还可以基于数组来实现一个简单的栈,这有助于你更深入地理解栈的底层原理。

```java
public class CustomStack {
private T[] array;
private int top;
private int capacity;

@SuppressWarnings("unchecked")
public CustomStack(int capacity) {this.capacity = capacity;this.array = (T[]) new Object[capacity];this.top = -1;
}// 入栈操作
public void push(T element) {if (isFull()) {throw new RuntimeException("栈已满");}array[++top] = element;
}// 出栈操作
public T pop() {if (isEmpty()) {throw new RuntimeException("栈为空");}return array[top--];
}// 获取栈顶元素
public T peek() {if (isEmpty()) {throw new RuntimeException("栈为空");}return array[top];
}// 判断栈是否为空
public boolean isEmpty() {return top == -1;
}// 判断栈是否已满
public boolean isFull() {return top == capacity - 1;
}// 获取栈的大小
public int size() {return top + 1;
}public static void main(String[] args) {CustomStack<Integer> stack = new CustomStack<>(3);stack.push(10);stack.push(20);stack.push(30);System.out.println(stack.pop()); // 输出: 30System.out.println(stack.peek()); // 输出: 20
}
 
 

}

http://www.hskmm.com/?act=detail&tid=35712

相关文章:

  • 读书笔记:Oracle分区技术详解
  • 2025精密光电厂家推荐:柯依努UV固化设备专业定制,品质保障!
  • 徐老师2025新版Nodejs课程含项目实战
  • 详细介绍:isis整体知识梳理
  • Moe-ctf Misc部分题解
  • DBA必备脚本:Oracle获取正在运行SQL的字面SQL文本
  • 智联笔记项目——251021为分享功能添加有效期
  • 直方图
  • 昂贵多目标优化
  • 操作系统应用开发(二十一)RustDesk 域名访问故障—东方仙盟筑基期 - 详解
  • 完整教程:【STM32】通用输入输出端口GPIO
  • Oracle数据库
  • AI元人文:岐金兰的“阴谋”
  • prufer板子
  • 军用混合现实头盔EagleEye的技术解析
  • 2025电子数据取证分析师WriteUp
  • 03.Python百行代码实现点赞系统
  • Promise多个then、catch、finally的执行结果分析与总结
  • Search-R1论文浅析与代码实现
  • Ai元人文构想:技术介入人文领域的辩证思考与路径探索
  • 2025年10月医用面膜产品推荐:权威对比评测榜助术后修护精准决策
  • 2025年10月电动叉车销售公司推荐:五强对比评测榜
  • 类方法和实例方法区别 flutter
  • 今天给电脑安装了新华财经
  • 2025电子数据取证分析师Wp
  • 2025年10月仓储管理系统推荐榜:鸿链云仓领衔对比评测排行
  • NITEX:构建时尚新供应链的数字平台与技术架构
  • 电子人速囤!正点原子万用表,电烙铁,电桥镊子等商品!
  • 2025年10月超声波清洗机厂家榜单:十家主流厂商横向对比
  • 2025年10月超声波清洗机厂家评价榜:实力对比一览