互联网大厂 Java 面试:核心知识、框架与中间件大考验
在互联网大厂的一间严肃的面试室内,气氛紧张而凝重。面试官正襟危坐,面前摆放着求职者的简历,而求职者王铁牛坐在对面,略显紧张地搓着手。
第一轮面试开始
- 面试官:首先问你几个 Java 核心知识的问题。Java 中基本数据类型有哪些?
- 王铁牛:这个我知道,有 byte、short、int、long、float、double、char、boolean。
- 面试官:回答得不错。那说说 Java 中多态的实现方式有哪些?
- 王铁牛:多态主要通过继承、接口和方法重写来实现。父类引用指向子类对象,调用重写的方法就能体现多态性。
- 面试官:很好,基础很扎实。那在 Java 中,异常处理机制是怎样的?
- 王铁牛:Java 异常分为受检查异常和非受检查异常。可以使用 try-catch-finally 语句来捕获和处理异常,也可以使用 throws 关键字将异常抛出。
第二轮面试开始
- 面试官:接下来考考你 JUC 和多线程的知识。什么是线程池,为什么要使用线程池?
- 王铁牛:线程池就是预先创建一组线程,当有任务提交时,从线程池中获取线程来执行任务。使用线程池可以减少线程创建和销毁的开销,提高系统性能。
- 面试官:回答得挺好。那说说线程池有哪些核心参数?
- 王铁牛:嗯……有核心线程数、最大线程数,还有……好像还有个队列长度。其他的我有点记不清了。
- 面试官:这里你回答了一部分。那再问你,在多线程环境下,如何保证数据的线程安全?
- 王铁牛:这个嘛,我觉得可以用锁吧,具体怎么用我不太确定。
第三轮面试开始
- 面试官:现在考察一下框架和中间件的知识。Spring 框架的核心特性有哪些?
- 王铁牛:Spring 核心特性有 IoC(控制反转)和 AOP(面向切面编程)。IoC 是把对象的创建和依赖关系的管理交给 Spring 容器,AOP 可以在不修改原有代码的情况下增加额外的功能。
- 面试官:回答得不错。那 Spring Boot 和 Spring 有什么区别?
- 王铁牛:Spring Boot 好像是简化了 Spring 的配置,让开发更方便,其他的我也说不太清楚。
- 面试官:好,那说说 Redis 的应用场景有哪些?
- 王铁牛:Redis 可以做缓存,还能做消息队列,其他的我就不太了解了。
面试官扶了扶眼镜,看着王铁牛说:“今天的面试就到这里,你整体对一些基础的知识点有一定的掌握,像 Java 核心知识部分回答得很好,说明基础还是比较扎实的。不过在 JUC、线程池以及一些框架和中间件的深入理解上还有所欠缺,对于一些复杂的问题回答得不够清晰和完整。你先回家等通知吧,后续如果有进一步的消息,我们会及时联系你。”
答案详解
- Java 中基本数据类型:
- 数值型:
- 整数类型:byte(1 字节,-128 到 127)、short(2 字节,-32768 到 32767)、int(4 字节,-2147483648 到 2147483647)、long(8 字节,范围更大)。
- 浮点类型:float(4 字节,单精度浮点数)、double(8 字节,双精度浮点数)。
- 字符型:char(2 字节,用于表示单个字符,采用 Unicode 编码)。
- 布尔型:boolean(只有两个值,true 和 false)。
- 数值型:
- Java 中多态的实现方式:
- 继承:子类继承父类,重写父类的方法。通过父类引用指向子类对象,调用重写的方法时会根据实际对象类型调用相应的方法。例如:
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");
}
}
// 使用
Animal animal = new Dog();
animal.sound(); // 输出 Dog barks
- **接口**:一个类实现多个接口,不同的实现类可以有不同的实现方式。例如:
interface Shape {
double area();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
}
- Java 异常处理机制:
- 受检查异常:编译器会检查这类异常,要求程序员必须进行处理,否则编译不通过。例如 IOException。处理方式可以使用 try-catch 块捕获异常,或者使用 throws 关键字将异常抛出给调用者处理。
try {
FileInputStream fis = new FileInputStream("test.txt");
} catch (IOException e) {
e.printStackTrace();
}
- **非受检查异常**:编译器不会检查这类异常,通常是由程序逻辑错误引起的,如 NullPointerException、ArrayIndexOutOfBoundsException 等。
- **finally 块**:无论 try 块中是否发生异常,finally 块中的代码都会执行,常用于释放资源。
try {
// 可能抛出异常的代码
} catch (Exception e) {
// 异常处理代码
} finally {
// 无论是否发生异常都会执行的代码
}
- 线程池:
- 定义:线程池是一种线程使用模式,预先创建一组线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完后线程不会销毁,而是返回线程池等待下一个任务。
- 使用原因:
- 减少线程创建和销毁的开销:频繁创建和销毁线程会消耗大量的系统资源,使用线程池可以避免这种开销。
- 提高响应速度:任务提交后可以立即从线程池中获取线程执行,无需等待线程创建。
- 便于线程管理:可以控制线程的数量、执行顺序等。
- 线程池核心参数:
- corePoolSize:核心线程数,线程池长期维持的线程数量,即使线程处于空闲状态也不会销毁。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。
- keepAliveTime:线程空闲时间,当线程空闲时间超过这个值时,非核心线程会被销毁。
- TimeUnit:时间单位,用于指定 keepAliveTime 的时间单位,如 TimeUnit.SECONDS 表示秒。
- workQueue:任务队列,用于存储等待执行的任务。常见的队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。
- threadFactory:线程工厂,用于创建线程。
- handler:任务拒绝策略,当任务队列已满且线程池中的线程数量达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(让提交任务的线程执行任务)等。
- 多线程环境下保证数据线程安全的方法:
- 使用锁机制:
- synchronized 关键字:可以修饰方法或代码块,保证同一时间只有一个线程可以访问被修饰的方法或代码块。例如:
- 使用锁机制:
public synchronized void add(int value) {
// 线程安全的操作
}
- **ReentrantLock**:是一个可重入锁,功能比 synchronized 更强大,可以实现公平锁和非公平锁。例如:
import java.util.concurrent.locks.ReentrantLock;
ReentrantLock lock = new ReentrantLock();
public void add(int value) {
lock.lock();
try {
// 线程安全的操作
} finally {
lock.unlock();
}
}
- **使用原子类**:如 AtomicInteger、AtomicLong 等,这些类使用 CAS(Compare-And-Swap)算法保证操作的原子性。例如:
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 线程安全的自增操作
- Spring 框架的核心特性:
- IoC(控制反转):也称为依赖注入(DI),把对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象本身来创建和管理依赖。例如:
// 定义一个接口
interface UserService {
void addUser();
}
// 实现类
class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("Add user");
}
}
// 通过 Spring 配置文件或注解注入依赖
- **AOP(面向切面编程)**:可以在不修改原有代码的情况下,对程序进行功能增强。例如,在方法执行前后添加日志记录、事务管理等功能。
// 定义切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void beforeMethod() {
System.out.println("Before method execution");
}
}
- Spring Boot 和 Spring 的区别:
- 配置简化:Spring 需要大量的 XML 配置文件或 Java 配置类来进行配置,而 Spring Boot 采用了约定大于配置的原则,通过自动配置减少了配置的工作量。例如,Spring Boot 只需要在 application.properties 或 application.yml 中进行简单的配置即可。
- 嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 等嵌入式服务器,不需要额外部署服务器,直接运行主类即可启动应用。
- 快速开发:Spring Boot 提供了大量的 Starter 依赖,只需要添加相应的依赖,Spring Boot 会自动配置相关的组件,加快开发速度。
- Redis 的应用场景:
- 缓存:将经常访问的数据存储在 Redis 中,减少数据库的访问压力,提高系统的响应速度。例如,将用户信息、商品信息等缓存到 Redis 中。
- 会话管理:在分布式系统中,可以使用 Redis 存储用户的会话信息,实现会话的共享和管理。
- 消息队列:Redis 提供了 List 数据结构,可以实现简单的消息队列。生产者将消息放入 List 中,消费者从 List 中取出消息进行处理。
- 分布式锁:利用 Redis 的原子操作和过期时间特性,可以实现分布式锁,保证在分布式系统中同一时间只有一个线程可以访问共享资源。
- 排行榜:使用 Redis 的 Sorted Set 数据结构可以实现排行榜功能,例如商品销量排行榜、用户积分排行榜等。