互联网大厂 Java 面试:核心知识、框架与中间件大考验
严肃的面试官端坐在办公桌后,面前放着王铁牛的简历。王铁牛紧张地坐在对面,双手不自觉地攥着衣角。
第一轮提问 面试官:先来问问 Java 核心知识。Java 中基本数据类型有哪些? 王铁牛:这个我知道,有 byte、short、int、long、float、double、char、boolean。 面试官:不错,回答得很准确。那在 Java 里,String 类为什么是不可变的呢? 王铁牛:因为 String 类被 final 修饰,它的底层是用 char 数组存储,数组也是被 final 修饰的,所以不可变。 面试官:很好,理解得挺到位。那说说 Java 中的多态是怎么实现的? 王铁牛:多态主要通过继承、接口实现和方法重写来实现。父类引用指向子类对象,调用方法时会根据实际对象类型调用相应的方法。 面试官:非常棒,基础很扎实。
第二轮提问 面试官:接下来聊聊 JUC 和多线程。线程有哪些状态? 王铁牛:有新建、就绪、运行、阻塞和死亡这几种状态。 面试官:对的。那在 Java 中,如何创建一个线程池? 王铁牛:可以通过 Executors 工具类创建,像 newFixedThreadPool、newCachedThreadPool 等,也可以通过 ThreadPoolExecutor 类手动创建。 面试官:回答得可以。那说说线程池的拒绝策略有哪些? 王铁牛:嗯……有 AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。 面试官:不错,看来有一定的了解。
第三轮提问 面试官:现在谈谈框架和中间件。Spring 的核心特性有哪些? 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。 面试官:那 Spring Boot 相比 Spring 有什么优势呢? 王铁牛:Spring Boot 简化了 Spring 项目的配置,它提供了自动配置,能快速搭建项目,还内置了服务器,方便开发和部署。 面试官:RabbitMQ 有哪些应用场景? 王铁牛:嗯……它可以用于异步处理、系统解耦和流量削峰。 面试官:好的。那 Redis 的数据类型有哪些? 王铁牛:有字符串、哈希、列表、集合、有序集合。 面试官:看来你对常见的还是知道的。不过再问你个深入点的,Redis 的持久化机制有哪些? 王铁牛:这个……好像有个什么快照,还有……我不太记得具体的了。 面试官:好吧,整体表现有好有坏。你先回家等通知吧,后续如果有结果会及时联系你。
答案详解
- Java 基本数据类型:
- Java 有 8 种基本数据类型,分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(理论上 1 位,但实际实现可能不同)。基本数据类型存储的是具体的值,它们的取值范围和存储大小是固定的。
- String 类不可变的原因:
- String 类被 final 修饰,意味着它不能被继承。其底层使用 char 数组(在 Java 9 及以后是 byte 数组)存储字符序列,并且该数组也被 final 修饰。这就使得 String 对象一旦创建,其内部的字符序列就不能被修改。如果对 String 对象进行拼接等操作,实际上是创建了一个新的 String 对象。不可变的好处包括线程安全、可以作为 HashMap 的键等。
- Java 多态的实现方式:
- 继承:子类继承父类,重写父类的方法。通过父类引用指向子类对象,在运行时会根据实际对象类型调用相应的方法。例如:
class Animal {
public void sound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Woof");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.sound(); // 输出 Woof
}
}
- **接口实现**:一个类实现一个或多个接口,重写接口中的方法。通过接口引用指向实现类对象,调用方法时会执行实现类的具体实现。
- **方法重写**:在子类中对父类的方法进行重新定义,方法名、参数列表和返回类型都要相同(返回类型可以是父类方法返回类型的子类),访问权限不能比父类更严格。
4. 线程的状态:
- 新建(New):线程对象被创建,但还没有调用 start() 方法。
- 就绪(Runnable):线程调用了 start() 方法,进入可运行状态,等待获取 CPU 时间片。
- 运行(Running):线程获得 CPU 时间片,正在执行 run() 方法中的代码。
- 阻塞(Blocked):线程因为某些原因(如等待 I/O、等待锁等)暂时停止执行,让出 CPU 资源。
- 死亡(Terminated):线程的 run() 方法执行完毕,或者因为异常退出,线程生命周期结束。
5. 创建线程池的方式:
- Executors 工具类:
- newFixedThreadPool:创建一个固定大小的线程池,线程数量固定,当有新任务提交时,如果线程池中有空闲线程则执行,否则任务会在队列中等待。
- newCachedThreadPool:创建一个可缓存的线程池,线程数量不固定,根据任务数量动态创建和销毁线程。如果线程池中有空闲线程且空闲时间超过 60 秒,则会被回收。
- newSingleThreadExecutor:创建一个单线程的线程池,保证所有任务按照顺序依次执行。
- ThreadPoolExecutor 类:可以手动配置线程池的核心线程数、最大线程数、线程空闲时间、任务队列等参数,更加灵活。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class ThreadPoolExample {
public static void main(String[] args) {
// 使用 Executors 创建固定大小线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 使用 ThreadPoolExecutor 手动创建线程池
ThreadPoolExecutor manualThreadPool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60L, // 线程空闲时间
java.util.concurrent.TimeUnit.SECONDS,
new java.util.concurrent.LinkedBlockingQueue<>(10) // 任务队列
);
}
}
- 线程池的拒绝策略:
- AbortPolicy:默认的拒绝策略,当线程池的任务队列已满且线程池中的线程数量达到最大线程数时,新提交的任务会被拒绝,并抛出 RejectedExecutionException 异常。
- CallerRunsPolicy:当任务被拒绝时,由提交任务的线程来执行该任务。
- DiscardPolicy:直接丢弃新提交的任务,不做任何处理。
- DiscardOldestPolicy:丢弃任务队列中最老的任务,然后尝试将新任务加入队列。
- Spring 的核心特性:
- IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。Spring 容器负责创建对象、管理对象的生命周期和依赖关系,通过配置文件或注解来实现。例如,使用 @Autowired 注解可以自动注入依赖的对象。
- AOP(面向切面编程):是一种编程范式,用于将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来。Spring AOP 通过代理模式实现,在不修改原有代码的情况下,在特定的切入点(如方法调用前后)织入额外的逻辑。
- Spring Boot 相比 Spring 的优势:
- 简化配置:Spring Boot 提供了自动配置机制,根据项目中引入的依赖自动配置 Spring 应用,减少了大量的 XML 配置文件。
- 快速搭建项目:可以使用 Spring Initializr 快速生成项目骨架,集成了常见的开发工具和依赖,提高开发效率。
- 内置服务器:Spring Boot 内置了 Tomcat、Jetty 等服务器,无需手动部署到外部服务器,方便开发和测试。
- RabbitMQ 的应用场景:
- 异步处理:将一些耗时的操作(如文件上传、数据分析等)放入消息队列中异步处理,提高系统的响应速度。例如,用户注册时,发送注册成功邮件可以通过消息队列异步处理,避免阻塞主线程。
- 系统解耦:不同的系统或模块之间通过消息队列进行通信,降低系统之间的耦合度。例如,订单系统和库存系统之间可以通过消息队列传递订单信息,当订单系统发生变化时,不会影响库存系统。
- 流量削峰:在高并发场景下,将请求放入消息队列中,服务器按照自身的处理能力从队列中取出请求进行处理,避免服务器因瞬间高流量而崩溃。
- Redis 的数据类型:
- 字符串(String):最基本的数据类型,可以存储字符串、整数或浮点数。支持对字符串的各种操作,如获取、设置、追加、递增等。
- 哈希(Hash):类似于 Java 中的 HashMap,是一个键值对的集合。适合存储对象信息,如用户信息、商品信息等。
- 列表(List):是一个有序的字符串列表,可以从列表的两端进行插入和删除操作。常用于实现消息队列、任务队列等。
- 集合(Set):是一个无序且唯一的字符串集合。支持集合的交集、并集、差集等操作,常用于去重、社交关系等场景。
- 有序集合(Sorted Set):是一个有序的字符串集合,每个元素都有一个分数,根据分数进行排序。常用于排行榜、热门列表等场景。
- Redis 的持久化机制:
- RDB(Redis Database):是一种快照持久化方式,将 Redis 内存中的数据在某个时间点保存到磁盘上的二进制文件中。可以通过配置定期执行快照操作,也可以手动执行 SAVE 或 BGSAVE 命令。RDB 恢复数据速度快,但可能会丢失最后一次快照之后的数据。
- AOF(Append Only File):是一种日志持久化方式,将 Redis 执行的写操作以日志的形式追加到文件末尾。可以通过配置不同的同步策略(如每秒同步、每次写操作同步等)来保证数据的安全性。AOF 文件相对较大,恢复数据速度相对较慢,但数据安全性高,基本不会丢失数据。