互联网大厂 Java 面试:核心知识、框架与中间件大考验
王铁牛怀揣着对互联网大厂的憧憬,坐在了面试官面前,一场紧张又充满挑战的面试即将拉开帷幕。
第一轮面试开始 面试官:“我们先从 Java 核心知识问起,Java 中多态的实现方式有哪些?” 王铁牛:“多态的实现方式主要有方法重载和方法重写。方法重载是在一个类中,有多个方法名相同但参数列表不同;方法重写是子类重写父类的方法,返回值和方法名相同,参数列表也相同。” 面试官:“回答得不错。那你说说 JVM 的内存结构是怎样的?” 王铁牛:“JVM 内存结构主要包括堆、栈、方法区、程序计数器和本地方法栈。堆是存放对象实例的地方;栈是每个线程私有的,用于存储局部变量等;方法区存放类的信息、常量等;程序计数器记录当前线程执行的字节码行号;本地方法栈为本地方法服务。” 面试官:“很好,基础很扎实。那在多线程中,线程的生命周期有哪些状态?” 王铁牛:“线程的生命周期有新建、就绪、运行、阻塞和死亡这几种状态。新建就是刚创建线程对象;就绪是线程具备了运行条件,等待 CPU 调度;运行就是线程正在执行;阻塞是线程因为某些原因暂停执行;死亡就是线程执行完毕或者出现异常终止。”
第二轮面试开始 面试官:“看来你基础掌握得挺好。那我们说说 JUC,CountDownLatch 和 CyclicBarrier 有什么区别?” 王铁牛:“嗯……这个……好像都是和线程同步有关的吧,具体区别我有点不太确定。” 面试官:“没关系,再思考一下。那线程池有哪些创建方式,分别适用于什么场景?” 王铁牛:“创建方式……好像可以用 Executors 工具类创建,还有自己配置参数创建。适用于什么场景我不太清楚了。” 面试官:“我们接着说数据结构,HashMap 在 JDK1.7 和 JDK1.8 中有哪些区别?” 王铁牛:“区别嘛,好像 1.8 用了红黑树,其他的我就不太记得了。”
第三轮面试开始 面试官:“我们来聊聊框架相关的。Spring 的 IOC 和 AOP 是什么,能举例说明它们的应用场景吗?” 王铁牛:“IOC 是控制反转,AOP 是面向切面编程。应用场景……比如 AOP 可以用在日志记录方面,IOC 我不太清楚有啥具体场景。” 面试官:“Spring Boot 相对于 Spring 有哪些优势?” 王铁牛:“好像是配置更简单,能快速搭建项目,其他的我也说不太清。” 面试官:“MyBatis 中 #{} 和 ${} 的区别是什么?” 王铁牛:“这个……我只记得它们都能传参数,具体区别就不知道了。”
面试官扶了扶眼镜,说道:“今天的面试就到这里吧,你回去等通知。我们会综合评估你的表现,之后会有专人联系你。希望你回去之后也可以继续巩固自己的知识,特别是在 JUC、框架和一些细节方面还有很大的提升空间。面试不仅仅是考察你当下的知识储备,也是一个让你发现自己不足并不断进步的过程。期待你之后能有更好的表现。”
答案详解
- 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 makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
- JVM 的内存结构
- 堆(Heap):是 JVM 中最大的一块内存区域,所有对象实例和数组都在这里分配内存。堆是线程共享的,垃圾回收主要针对这块区域。
- 栈(Stack):每个线程都有自己的栈,栈中存储局部变量、方法调用信息等。栈是线程私有的,它的生命周期和线程相同。
- 方法区(Method Area):用于存储类的信息、常量、静态变量等。在 JDK1.8 之前叫永久代,JDK1.8 及以后叫元空间。
- 程序计数器(Program Counter Register):记录当前线程执行的字节码行号,线程私有,保证线程切换后能恢复到正确的执行位置。
- 本地方法栈(Native Method Stack):和栈类似,不过它是为本地方法服务的。
- 线程的生命周期状态
- 新建(New):通过 new 关键字创建线程对象,此时线程还没有开始执行。
- 就绪(Runnable):调用线程的 start() 方法后,线程进入就绪状态,等待 CPU 调度。
- 运行(Running):CPU 分配时间片给该线程,线程开始执行。
- 阻塞(Blocked):线程因为某些原因暂停执行,比如等待锁、调用 sleep() 方法等。
- 死亡(Terminated):线程执行完毕或者出现异常终止。
- CountDownLatch 和 CyclicBarrier 的区别
- CountDownLatch:是一个计数器,初始时设置一个计数值,线程可以调用 countDown() 方法将计数值减 1,其他线程可以调用 await() 方法等待计数值变为 0。它是一次性的,计数值减到 0 后不能再使用。适用于一个或多个线程等待其他线程完成任务的场景,比如主线程等待多个子线程完成数据加载。
- CyclicBarrier:它也有一个初始计数值,多个线程调用 await() 方法,当调用次数达到计数值时,所有等待的线程会同时继续执行。它可以重复使用,适用于多个线程相互等待,达到某个条件后一起继续执行的场景,比如多个运动员准备好后一起起跑。
- 线程池的创建方式及适用场景
- 使用 Executors 工具类创建
- newFixedThreadPool(int nThreads):创建一个固定大小的线程池,适用于需要控制并发线程数量的场景,比如服务器处理客户端请求。
- newCachedThreadPool():创建一个可缓存的线程池,线程数量会根据任务的多少自动调整,适用于执行大量短期异步任务的场景。
- newSingleThreadExecutor():创建一个单线程的线程池,保证任务按顺序执行,适用于需要顺序执行任务的场景。
- 自定义线程池:通过 ThreadPoolExecutor 类自己配置核心线程数、最大线程数、线程空闲时间等参数。适用于对线程池参数有特殊要求的场景。
- 使用 Executors 工具类创建
- HashMap 在 JDK1.7 和 JDK1.8 中的区别
- 数据结构:JDK1.7 采用数组 + 链表的结构,JDK1.8 采用数组 + 链表 + 红黑树的结构。当链表长度超过 8 且数组长度大于 64 时,链表会转换为红黑树,提高查找效率。
- 插入方式:JDK1.7 采用头插法,JDK1.8 采用尾插法,避免了多线程下的死循环问题。
- 扩容机制:JDK1.8 对扩容算法进行了优化,不需要重新计算 hash 值。
- Spring 的 IOC 和 AOP 及应用场景
- IOC(Inversion of Control,控制反转):是一种设计思想,将对象的创建和依赖关系的管理交给 Spring 容器。应用场景比如在 Web 项目中,将 DAO、Service 等对象的创建和依赖注入交给 Spring 容器,降低代码的耦合度。例如:
public class UserService {
private UserDao userDao;
// 通过构造函数注入
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
- **AOP(Aspect-Oriented Programming,面向切面编程)**:在不修改原有代码的基础上,对程序进行增强。应用场景比如日志记录、事务管理等。例如,使用 AOP 实现日志记录:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
}
- Spring Boot 相对于 Spring 的优势
- 自动配置:Spring Boot 提供了大量的自动配置,减少了开发者的配置工作量,能快速搭建项目。
- 嵌入式服务器:内置了 Tomcat、Jetty 等服务器,无需手动部署,直接运行项目即可。
- 起步依赖:通过添加不同的起步依赖,快速集成各种功能,比如 Spring Boot Starter Web 可以快速搭建 Web 项目。
- Actuator:提供了应用监控和管理的功能,方便开发者了解应用的运行状态。
- MyBatis 中 #{} 和 ${} 的区别
- #{}:是预编译处理,MyBatis 会将 #{} 替换为占位符?,可以防止 SQL 注入。例如:
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是字符串替换,MyBatis 会直接将 ${} 替换为传入的值,可能会导致 SQL 注入。一般用于动态表名、列名等场景。例如:
<select id="getUserByColumn" parameterType="map" resultType="User">
SELECT * FROM users WHERE ${column} = #{value}
</select>