互联网大厂 Java 面试:核心知识、框架与中间件大考验
在互联网大厂的一间安静面试室内,严肃的面试官正对面坐着略显紧张的求职者王铁牛,一场考验 Java 综合知识的面试即将拉开帷幕。
第一轮提问 面试官:首先问几个基础的 Java 核心知识问题。Java 中多态的实现方式有哪些? 王铁牛:这个我知道,Java 多态的实现方式主要有方法重载和方法重写。方法重载是在同一个类中,方法名相同但参数列表不同;方法重写是子类重写父类的方法,方法名、参数列表和返回值类型都一样。 面试官:回答得不错。那再说说 Java 中抽象类和接口的区别。 王铁牛:抽象类可以有构造方法、普通方法和抽象方法,而接口只能有抽象方法(Java 8 以后可以有默认方法和静态方法)。一个类只能继承一个抽象类,但可以实现多个接口。 面试官:很好,基础很扎实。那 String、StringBuffer 和 StringBuilder 有什么区别? 王铁牛:String 是不可变的,每次对 String 进行操作都会生成一个新的 String 对象。而 StringBuffer 和 StringBuilder 是可变的,StringBuffer 是线程安全的,它的方法大多都加了 synchronized 关键字;StringBuilder 是非线程安全的,但是它的性能比 StringBuffer 高。
第二轮提问 面试官:进入 JUC 和多线程相关的问题。线程有哪些状态? 王铁牛:线程有新建、就绪、运行、阻塞和死亡这几种状态。新建就是刚创建线程对象,就绪是线程准备好可以运行了,运行就是线程正在执行,阻塞是线程因为某些原因暂停执行,死亡就是线程执行完毕。 面试官:不错。那说说线程池的工作原理。 王铁牛:嗯……线程池就是提前创建好一些线程,当有任务提交时,就从线程池中拿一个空闲的线程来执行任务。如果线程池里没有空闲线程,任务就会被放到队列里等待。 面试官:那线程池的拒绝策略有哪些? 王铁牛:这个……好像有什么丢弃任务,还有……我有点不太确定了。
第三轮提问 面试官:接下来是框架和中间件的问题。Spring 的 AOP 实现原理是什么? 王铁牛:AOP 嘛,就是面向切面编程,实现原理好像是用了代理模式,有 JDK 动态代理和 CGLIB 代理。 面试官:那 Spring Boot 的自动配置原理呢? 王铁牛:这个……好像是根据 classpath 下的依赖和配置文件来自动配置一些 Bean,具体怎么弄的我不太清楚了。 面试官:最后问一下,Redis 的持久化机制有哪些? 王铁牛:好像有 RDB 和 AOF,但是具体它们是怎么工作的我就说不太明白了。
面试官推了推眼镜,说道:“今天的面试就到这里,你回去等通知吧。整体来看,你对一些基础的 Java 知识掌握得还可以,但是在一些稍微复杂的知识点,像线程池的拒绝策略、Spring Boot 自动配置原理和 Redis 持久化机制的细节上,回答得不够清晰和完整。我们后续会综合评估,有结果会尽快通知你。”
答案详解
- Java 多态的实现方式
- 方法重载:在同一个类中,方法名相同但参数列表不同(参数的类型、个数、顺序不同)。方法重载主要是为了方便程序员使用,提高代码的可读性和可维护性。例如:
public class OverloadExample {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
- **方法重写**:子类重写父类的方法,方法名、参数列表和返回值类型都一样(返回值类型可以是父类方法返回值类型的子类)。方法重写体现了子类对父类方法的扩展和修改,是实现多态的重要方式。例如:
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");
}
}
- 抽象类和接口的区别
- 构造方法:抽象类可以有构造方法,用于子类初始化父类部分的属性;接口不能有构造方法。
- 方法类型:抽象类可以有普通方法和抽象方法;接口在 Java 8 之前只能有抽象方法,Java 8 以后可以有默认方法和静态方法。
- 继承和实现:一个类只能继承一个抽象类,但可以实现多个接口。这使得接口更适合用于定义一组规范,而抽象类更适合作为一些具有部分共性的类的基类。
- String、StringBuffer 和 StringBuilder 的区别
- 可变性:String 是不可变的,每次对 String 进行操作(如拼接、替换等)都会生成一个新的 String 对象,这会导致大量的内存开销;StringBuffer 和 StringBuilder 是可变的,它们内部有一个字符数组,当进行操作时,会直接在这个数组上进行修改,不会生成新的对象。
- 线程安全性:StringBuffer 是线程安全的,它的方法大多都加了 synchronized 关键字,保证在多线程环境下操作的安全性;StringBuilder 是非线程安全的,但是它的性能比 StringBuffer 高,因为没有同步的开销。
- 线程的状态
- 新建(New):当创建一个线程对象(如
Thread t = new Thread())时,线程处于新建状态,此时线程还没有开始执行。 - 就绪(Runnable):调用线程的
start()方法后,线程进入就绪状态,此时线程已经准备好可以运行,等待操作系统的调度。 - 运行(Running):当线程获得 CPU 时间片后,进入运行状态,开始执行线程的
run()方法。 - 阻塞(Blocked):线程因为某些原因(如等待 I/O 操作、等待锁等)暂停执行,进入阻塞状态。当阻塞的原因解除后,线程会重新进入就绪状态。
- 死亡(Terminated):线程执行完
run()方法或者因为异常终止,进入死亡状态,此时线程生命周期结束。
- 新建(New):当创建一个线程对象(如
- 线程池的工作原理
- 线程池内部维护了一个线程集合和一个任务队列。当有任务提交时,线程池会按照以下步骤处理:
- 如果线程池中的线程数量小于核心线程数,会创建一个新的线程来执行任务。
- 如果线程池中的线程数量大于等于核心线程数,任务会被放入任务队列中等待。
- 如果任务队列已满,且线程池中的线程数量小于最大线程数,会创建一个新的线程来执行任务。
- 如果任务队列已满,且线程池中的线程数量大于等于最大线程数,会根据线程池的拒绝策略来处理任务。
- 线程池内部维护了一个线程集合和一个任务队列。当有任务提交时,线程池会按照以下步骤处理:
- 线程池的拒绝策略
- AbortPolicy:默认的拒绝策略,当任务被拒绝时,会抛出
RejectedExecutionException异常。 - CallerRunsPolicy:由提交任务的线程来执行该任务。
- DiscardPolicy:直接丢弃该任务,不做任何处理。
- DiscardOldestPolicy:丢弃任务队列中最老的任务,然后尝试将新任务加入队列。
- AbortPolicy:默认的拒绝策略,当任务被拒绝时,会抛出
- Spring 的 AOP 实现原理
- Spring 的 AOP 主要基于代理模式实现,有两种代理方式:
- JDK 动态代理:当目标对象实现了接口时,Spring 会使用 JDK 动态代理。JDK 动态代理通过
java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。它会在运行时创建一个代理类,该代理类实现了目标对象的接口,并在调用目标方法时插入切面逻辑。 - CGLIB 代理:当目标对象没有实现接口时,Spring 会使用 CGLIB 代理。CGLIB 是一个开源的代码生成库,它通过继承目标对象的方式来创建代理类,并在子类中重写目标方法,插入切面逻辑。
- JDK 动态代理:当目标对象实现了接口时,Spring 会使用 JDK 动态代理。JDK 动态代理通过
- Spring 的 AOP 主要基于代理模式实现,有两种代理方式:
- Spring Boot 的自动配置原理
- Spring Boot 的自动配置主要基于
@EnableAutoConfiguration注解,该注解会触发自动配置机制。具体步骤如下:- Spring Boot 在启动时会扫描 classpath 下的依赖和配置文件。
- 根据
META-INF/spring.factories文件中的配置,找到所有的自动配置类。 - 这些自动配置类会根据条件注解(如
@ConditionalOnClass、@ConditionalOnMissingBean等)来判断是否需要进行自动配置。如果条件满足,就会创建相应的 Bean 并注入到 Spring 容器中。
- Spring Boot 的自动配置主要基于
- Redis 的持久化机制
- RDB(Redis Database):RDB 是 Redis 的一种快照持久化方式,它会在某个时间点将 Redis 内存中的数据快照保存到磁盘上的一个二进制文件中。RDB 的优点是文件紧凑,恢复速度快;缺点是可能会丢失最后一次快照到发生故障之间的数据。
- AOF(Append Only File):AOF 是 Redis 的一种日志持久化方式,它会将 Redis 执行的每一条写命令追加到一个文件中。当 Redis 重启时,会重新执行这些命令来恢复数据。AOF 的优点是数据安全性高,几乎不会丢失数据;缺点是文件体积较大,恢复速度相对较慢。