在互联网大厂的一间明亮的面试室内,严肃的面试官坐在桌前,对面坐着紧张的求职者王铁牛。一场关于 Java 核心知识的面试即将拉开帷幕。
第一轮面试 面试官:首先问你几个基础的 Java 核心知识问题。Java 中的基本数据类型有哪些? 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:不错。那说说 Java 中多态的实现方式有哪些? 王铁牛:多态的实现方式主要有继承和接口。通过父类引用指向子类对象,或者接口引用指向实现类对象来实现。 面试官:回答得很清晰。那在 Java 中,String 类为什么是不可变的? 王铁牛:因为 String 类是用 final 修饰的,它的底层是一个 final 修饰的字符数组,一旦创建,其值不能被改变。
第二轮面试 面试官:接下来我问你一些关于 JUC、JVM 和多线程的问题。JUC 包中常用的并发工具类有哪些? 王铁牛:有 CountDownLatch、CyclicBarrier、Semaphore 等。 面试官:很好。那说说 JVM 的内存结构是怎样的? 王铁牛:JVM 内存结构主要包括堆、栈、方法区、本地方法栈和程序计数器。堆是存放对象实例的地方,栈是存储局部变量和方法调用信息的。 面试官:那在多线程编程中,如何避免死锁? 王铁牛:可以通过按顺序加锁、设置锁超时时间等方法来避免死锁。 面试官:回答得不错。那线程池的核心参数有哪些? 王铁牛:有核心线程数、最大线程数、空闲线程存活时间、任务队列和拒绝策略。
第三轮面试 面试官:最后问你一些关于框架和中间件的问题。Spring 框架的核心特性有哪些? 王铁牛:Spring 核心特性有 IoC(控制反转)和 AOP(面向切面编程)。 面试官:很好。那 Spring Boot 是如何实现自动配置的? 王铁牛:这个……好像是根据类路径下的依赖和配置文件来自动配置的。(回答得不太清晰) 面试官:那 MyBatis 中 #{} 和 ${} 的区别是什么? 王铁牛:呃……好像一个是预编译,一个不是预编译,具体我有点记不清了。(回答含糊) 面试官:Dubbo 是如何进行服务调用的? 王铁牛:这个……我就知道是远程服务调用,具体过程不太清楚。(回答不完整) 面试官:RabbitMQ 中的 Exchange 有哪些类型? 王铁牛:我只记得有 Direct 类型,其他的不太确定了。(回答不全面)
面试结束,面试官表情严肃地说:“王铁牛,今天的面试就到这里。你对一些基础问题回答得还可以,但对于一些复杂的框架和中间件的问题,回答得不够清晰和完整。我们会综合评估你的表现,你先回家等通知吧。”
答案解析
- Java 基本数据类型:Java 有 8 种基本数据类型,分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(1 位)。基本数据类型直接存储值,而不是引用。
- Java 多态的实现方式:
- 继承:通过父类引用指向子类对象,在运行时根据实际对象类型调用相应的方法。例如:
class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.eat(); // 输出 Dog is eating
}
}
- **接口**:接口引用指向实现类对象,调用接口方法时会执行实现类中的具体实现。
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
public class Main {
public static void main(String[] args) {
Shape shape = new Circle();
shape.draw(); // 输出 Drawing a circle
}
}
- String 类不可变的原因:String 类被 final 修饰,其底层的字符数组也是 final 修饰的。这意味着一旦 String 对象被创建,它的字符数组引用不能被改变,数组中的元素也不能被修改。不可变的好处有:线程安全、可以作为 HashMap 的键、提高缓存性能等。
- JUC 包中常用的并发工具类:
- CountDownLatch:允许一个或多个线程等待其他线程完成操作。例如,主线程等待多个子线程完成任务后再继续执行。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread finished");
latch.countDown();
}).start();
}
latch.await();
System.out.println("All threads finished");
}
}
- **CyclicBarrier**:让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续执行。
- **Semaphore**:用于控制同时访问某个资源的线程数量。可以用于限流等场景。
5. JVM 的内存结构: - 堆:是 JVM 中最大的一块内存区域,用于存放对象实例和数组。垃圾回收主要针对堆进行。 - 栈:每个线程都有自己的栈,用于存储局部变量、方法调用信息等。栈帧是栈的基本单位,每个方法调用都会创建一个栈帧。 - 方法区:用于存储类的信息、常量、静态变量等。在 JDK 1.8 之后,方法区被元空间取代。 - 本地方法栈:与栈类似,不过是为本地方法服务的。 - 程序计数器:记录当前线程执行的字节码行号。 6. 避免死锁的方法: - 按顺序加锁:所有线程按照相同的顺序获取锁,避免循环等待。 - 设置锁超时时间:如果线程在一定时间内无法获取锁,就放弃等待,避免一直阻塞。 - 使用可重入锁:可重入锁可以避免一个线程多次获取同一把锁时出现死锁。 7. 线程池的核心参数: - 核心线程数:线程池中长期存活的线程数量。 - 最大线程数:线程池允许创建的最大线程数量。 - 空闲线程存活时间:当线程池中的线程数量超过核心线程数时,空闲线程在多长时间内会被销毁。 - 任务队列:用于存储待执行的任务。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。 - 拒绝策略:当任务队列已满且线程池中的线程数量达到最大线程数时,新提交的任务的处理策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(让提交任务的线程执行任务)等。 8. Spring 框架的核心特性: - IoC(控制反转):将对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象自己创建和管理依赖。例如,通过 XML 配置或注解将对象的创建和依赖注入交给 Spring。 - AOP(面向切面编程):通过切面将横切关注点(如日志、事务管理等)与业务逻辑分离,提高代码的可维护性和复用性。可以通过注解或 XML 配置来定义切面和切点。 9. Spring Boot 自动配置原理:Spring Boot 通过 @EnableAutoConfiguration 注解开启自动配置功能。它会根据类路径下的依赖和配置文件,自动配置 Spring 应用的各种组件。Spring Boot 会扫描 META - INF/spring.factories 文件,该文件中定义了各种自动配置类。Spring Boot 会根据条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean 等)来判断是否需要进行自动配置。 10. MyBatis 中 #{} 和 ${} 的区别: - #{}:是预编译处理,MyBatis 会将 #{} 替换为占位符?,可以防止 SQL 注入。例如:
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是字符串替换,MyBatis 会直接将 ${} 替换为实际的值,可能会导致 SQL 注入。例如:
<select id="getUserByTableName" parameterType="String" resultType="User">
SELECT * FROM ${tableName}
</select>
- Dubbo 服务调用过程:
- 服务注册:服务提供者将服务信息(如服务接口、服务地址等)注册到注册中心(如 Zookeeper)。
- 服务发现:服务消费者从注册中心获取服务提供者的信息。
- 远程调用:服务消费者通过代理对象调用服务提供者的方法,底层通过网络传输协议(如 Dubbo 协议、HTTP 协议等)进行远程调用。
- 负载均衡:当有多个服务提供者时,Dubbo 会根据负载均衡策略(如随机、轮询等)选择一个服务提供者进行调用。
- RabbitMQ 中的 Exchange 类型:
- Direct Exchange:直连交换机,根据消息的路由键(routing key)将消息路由到对应的队列。
- Fanout Exchange:扇形交换机,将接收到的消息广播到所有绑定的队列。
- Topic Exchange:主题交换机,根据消息的路由键和绑定键的匹配规则将消息路由到对应的队列。
- Headers Exchange:头交换机,根据消息的头信息(headers)来进行消息路由。