互联网大厂 Java 面试:核心知识、框架与中间件大考验
在互联网大厂的一间安静的面试室内,严肃的面试官坐在桌前,对面坐着略显紧张的求职者王铁牛。一场对 Java 核心知识、框架及中间件的考验即将拉开帷幕。
第一轮提问 面试官:首先问几个 Java 基础问题。Java 中多态的实现方式有哪些? 王铁牛:这个我知道,Java 多态的实现方式主要有方法重载和方法重写。方法重载是在同一个类中,方法名相同但参数列表不同;方法重写是子类重写父类的方法。 面试官:回答得不错。那说说 HashMap 的底层数据结构。 王铁牛:HashMap 在 JDK 1.8 之前是数组加链表,JDK 1.8 之后是数组、链表和红黑树。当链表长度大于 8 且数组长度大于 64 时,链表会转化为红黑树,以提高查找效率。 面试官:很好。那 ArrayList 和 LinkedList 的区别是什么? 王铁牛:ArrayList 是基于数组实现的,它的优点是随机访问速度快,因为可以通过数组下标直接访问元素;缺点是插入和删除操作效率低,可能需要移动大量元素。LinkedList 是基于双向链表实现的,插入和删除操作效率高,因为只需要修改指针;但随机访问速度慢,需要从头或尾开始遍历链表。 面试官:非常棒,基础掌握得很扎实。
第二轮提问 面试官:进入 JUC 和多线程部分。什么是线程池?为什么要使用线程池? 王铁牛:线程池就是预先创建好一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务。使用线程池可以减少线程创建和销毁的开销,提高系统的性能和稳定性,还可以对线程进行统一的管理和监控。 面试官:那线程池有哪些核心参数? 王铁牛:线程池的核心参数有 corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程空闲存活时间)、unit(时间单位)、workQueue(任务队列)和 threadFactory(线程工厂)、handler(拒绝策略)。 面试官:当线程池的任务队列满了,并且线程数达到最大线程数,再提交任务会怎样? 王铁牛:呃……这个……好像会有什么策略处理,但我记不太清具体是怎样了。 面试官:这里会根据你设置的拒绝策略来处理,比如 AbortPolicy 会直接抛出异常,CallerRunsPolicy 会让提交任务的线程自己执行任务等。不过你前面回答得还可以。
第三轮提问 面试官:现在来考考框架相关的。Spring 的 IOC 和 AOP 是什么? 王铁牛:IOC 是控制反转,就是把对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象自己创建和管理。AOP 是面向切面编程,它可以在不修改原有代码的情况下,对程序进行增强,比如实现日志记录、事务管理等功能。 面试官:Spring Boot 和 Spring 有什么关系? 王铁牛:Spring Boot 是基于 Spring 构建的,它简化了 Spring 应用的开发,提供了自动配置功能,让开发者可以更快速地搭建和运行 Spring 应用。 面试官:MyBatis 是如何实现 SQL 映射的? 王铁牛:这个……大概是通过 XML 文件或者注解来定义 SQL 语句和 Java 对象的映射关系,但具体怎么实现的我有点说不清楚。 面试官:MyBatis 会通过 XML 或者注解中的配置,将 SQL 语句和 Java 对象的属性进行绑定,在执行 SQL 时进行数据的映射和转换。你前面对于 Spring 相关的回答还不错。
面试总结 面试官看着王铁牛,严肃地说:“通过这轮面试,能看出你对 Java 的一些基础核心知识掌握得比较扎实,像 Java 多态、HashMap、ArrayList 这些问题都回答得很好,说明你在基础方面下了不少功夫。在 JUC 和多线程部分,你对于线程池的基本概念和核心参数也有一定的了解,只是对于一些细节问题,比如任务队列满后的处理策略没有回答完整。在框架方面,你对 Spring 的 IOC 和 AOP 以及 Spring Boot 和 Spring 的关系理解得比较清晰,但对于 MyBatis 的 SQL 映射实现细节没有说清楚。综合来看,你的表现有亮点,但也存在一些知识漏洞。你先回家等通知吧,后续如果有进一步的消息,我们会及时联系你。”
问题答案详细解析
第一轮
- Java 中多态的实现方式有哪些?
- 方法重载(Overloading):在同一个类中,方法名相同,但参数列表不同(参数的类型、个数、顺序不同)。方法重载是编译时多态,编译器会根据调用方法时传入的参数类型、个数和顺序来决定调用哪个方法。例如:
public class OverloadExample {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
- **方法重写(Overriding)**:子类重写父类的方法,方法名、参数列表和返回值类型都相同。方法重写是运行时多态,在运行时,根据对象的实际类型来决定调用哪个类的方法。例如:
class Animal {
public void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
- 说说 HashMap 的底层数据结构。
- JDK 1.8 之前:HashMap 底层是数组加链表的结构。数组是 HashMap 的主体,每个数组元素是一个链表的头节点。当发生哈希冲突时(即不同的键通过哈希函数计算得到相同的数组下标),新的键值对会以链表的形式添加到对应的链表中。
- JDK 1.8 之后:采用数组、链表和红黑树的结构。当链表长度大于 8 且数组长度大于 64 时,链表会转化为红黑树。红黑树是一种自平衡的二叉搜索树,它可以将链表的查找、插入和删除操作的时间复杂度从 O(n) 降低到 O(log n),提高了 HashMap 在处理大量哈希冲突时的性能。当红黑树的节点数小于 6 时,红黑树会退化为链表。
- ArrayList 和 LinkedList 的区别是什么?
- 数据结构:ArrayList 基于数组实现,它在内存中是连续存储的;LinkedList 基于双向链表实现,每个节点包含数据、指向前一个节点的引用和指向后一个节点的引用。
- 随机访问性能:ArrayList 随机访问速度快,因为可以通过数组下标直接访问元素,时间复杂度为 O(1);LinkedList 随机访问速度慢,需要从头或尾开始遍历链表,时间复杂度为 O(n)。
- 插入和删除性能:ArrayList 在插入和删除元素时,可能需要移动大量元素,尤其是在数组中间插入或删除元素,时间复杂度为 O(n);LinkedList 在插入和删除元素时,只需要修改指针,时间复杂度为 O(1),但如果是在指定位置插入或删除元素,需要先遍历到该位置,时间复杂度为 O(n)。
- 内存占用:ArrayList 需要预先分配一定的内存空间,可能会存在空间浪费;LinkedList 每个节点需要额外的引用空间,整体内存开销相对较大。
第二轮
- 什么是线程池?为什么要使用线程池?
- 线程池定义:线程池是一种多线程处理形式,它预先创建好一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务。任务执行完后,线程不会销毁,而是返回线程池等待下一个任务。
- 使用线程池的原因:
- 减少线程创建和销毁的开销:创建和销毁线程需要消耗系统资源,使用线程池可以避免频繁创建和销毁线程,提高系统性能。
- 提高响应速度:当有任务提交时,线程池中有空闲线程可以立即执行任务,不需要等待线程创建。
- 统一管理和监控线程:线程池可以对线程进行统一的管理和监控,比如设置线程的最大数量、线程的空闲时间等,方便对系统进行调优和维护。
- 线程池有哪些核心参数?
- corePoolSize(核心线程数):线程池中的核心线程数量,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。
- maximumPoolSize(最大线程数):线程池允许创建的最大线程数量。当任务队列满了,并且线程数达到核心线程数时,线程池会创建新的线程,直到线程数达到最大线程数。
- keepAliveTime(线程空闲存活时间):当线程池中的线程数量大于核心线程数时,空闲线程在等待新任务的最长时间。超过这个时间,空闲线程会被销毁。
- unit(时间单位):keepAliveTime 的时间单位,如 TimeUnit.SECONDS、TimeUnit.MILLISECONDS 等。
- workQueue(任务队列):用于存储等待执行的任务的队列。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
- threadFactory(线程工厂):用于创建线程的工厂,可以自定义线程的名称、优先级等属性。
- handler(拒绝策略):当任务队列满了,并且线程数达到最大线程数时,再提交任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(让提交任务的线程自己执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务,然后尝试提交新任务)。
- 当线程池的任务队列满了,并且线程数达到最大线程数,再提交任务会怎样?
- 当线程池的任务队列满了,并且线程数达到最大线程数时,会根据设置的拒绝策略来处理新提交的任务:
- AbortPolicy:直接抛出 RejectedExecutionException 异常,阻止系统正常工作。
- CallerRunsPolicy:让提交任务的线程自己执行该任务,这样可以减缓新任务的提交速度。
- DiscardPolicy:直接丢弃新提交的任务,不做任何处理。
- DiscardOldestPolicy:丢弃任务队列中最老的任务,然后尝试将新任务加入队列。
- 当线程池的任务队列满了,并且线程数达到最大线程数时,会根据设置的拒绝策略来处理新提交的任务:
第三轮
- Spring 的 IOC 和 AOP 是什么?
- IOC(控制反转):控制反转是一种设计思想,它将对象的创建和依赖关系的管理从对象本身转移到外部的容器(Spring 容器)中。在传统的编程中,对象之间的依赖关系是由对象自己创建和管理的,而在 IOC 模式下,对象的创建和依赖关系的注入由 Spring 容器负责。例如,一个 Service 类依赖于一个 Dao 类,在 IOC 模式下,Spring 容器会创建 Dao 对象,并将其注入到 Service 类中。
- AOP(面向切面编程):AOP 是一种编程范式,它可以在不修改原有代码的情况下,对程序进行增强。AOP 将程序中的一些通用功能(如日志记录、事务管理等)提取出来,形成一个独立的模块(切面),然后在需要的地方进行切入。AOP 的实现原理主要有静态代理、动态代理(JDK 动态代理和 CGLIB 动态代理)。例如,在一个业务方法执行前后进行日志记录,可以通过 AOP 来实现,而不需要在每个业务方法中添加日志记录的代码。
- Spring Boot 和 Spring 有什么关系?
- Spring Boot 是基于 Spring 构建的,它是 Spring 的一个扩展和简化。Spring 是一个强大的 Java 开发框架,提供了 IOC、AOP 等功能,但在开发 Spring 应用时,需要进行大量的配置工作。Spring Boot 旨在简化 Spring 应用的开发,它提供了自动配置功能,通过约定大于配置的原则,减少了开发者的配置工作量。Spring Boot 还集成了各种常用的开发工具和中间件,如嵌入式服务器(Tomcat、Jetty 等)、数据库连接池、安全框架等,让开发者可以更快速地搭建和运行 Spring 应用。
- MyBatis 是如何实现 SQL 映射的?
- XML 配置方式:在 XML 文件中,通过
<mapper>标签定义 SQL 语句和 Java 对象的映射关系。例如:
- XML 配置方式:在 XML 文件中,通过
<mapper namespace="com.example.UserMapper">
<select id="getUserById" parameterType="int" resultType="com.example.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
- **注解方式**:在 Java 接口的方法上使用注解来定义 SQL 语句和映射关系。例如:
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(int id);
}
MyBatis 在执行 SQL 时,会根据配置的映射关系,将 SQL 语句的查询结果映射到 Java 对象的属性中,或者将 Java 对象的属性值作为参数传递给 SQL 语句。同时,MyBatis 还提供了一些高级特性,如动态 SQL、关联查询等,方便开发者进行复杂的数据库操作。