互联网大厂 Java 面试:核心知识、框架与中间件大考验
王铁牛怀揣着进入互联网大厂的梦想,坐在了面试官面前,一场决定命运的面试拉开了帷幕。
第一轮提问 面试官:首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些? 王铁牛:这个我知道,有 byte、short、int、long、float、double、char、boolean。 面试官:不错,回答得很准确。那 Java 中重载和重写的区别是什么? 王铁牛:重载是在一个类中,方法名相同但参数列表不同;重写是子类对父类方法的重新实现,方法名、参数列表和返回值类型都一样。 面试官:非常棒,基础很扎实。那 String、StringBuilder 和 StringBuffer 有什么区别呢? 王铁牛:String 是不可变的,每次对 String 的操作都会生成新的 String 对象;StringBuilder 是可变的,线程不安全,效率高;StringBuffer 也是可变的,线程安全,效率相对低一些。 面试官:很好,看来你对 Java 核心知识掌握得很牢固。
第二轮提问 面试官:接下来考考你 JUC 和多线程相关的知识。什么是线程池?为什么要使用线程池? 王铁牛:线程池就是管理线程的一个池子,使用线程池可以减少线程创建和销毁的开销,提高性能,还能控制并发线程的数量。 面试官:回答得不错。那线程池有哪些重要的参数? 王铁牛:这个嘛……有核心线程数、最大线程数,还有……还有个啥来着,我有点记不清了。 面试官:还有阻塞队列、线程存活时间和拒绝策略。那你说说常见的线程池有哪些? 王铁牛:好像有 FixedThreadPool、CachedThreadPool,其他的我就不太清楚了。 面试官:你回答对了一部分,还有 SingleThreadPool 和 ScheduledThreadPool 等。整体对线程池有一定了解,但还不够全面。
第三轮提问 面试官:现在来聊聊框架和中间件。Spring 框架的核心特性有哪些? 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。 面试官:不错,那 Spring Boot 相比 Spring 有什么优势? 王铁牛:Spring Boot 好像可以快速搭建项目,减少配置,但是具体为啥我也说不太清楚。 面试官:Spring Boot 提供了自动配置的功能,能让开发者快速上手,减少大量的 XML 配置。那 MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:这个……我好像听说过,但是不太确定,好像和 SQL 注入有关? **面试官**:#{} 是预编译处理,能防止 SQL 注入,{} 是字符串替换,可能会有 SQL 注入风险。你对框架的理解还不够深入。
面试官扶了扶眼镜,看着王铁牛说:“今天的面试就到这里,你回去等通知吧。你对一些基础的 Java 知识掌握得还可以,但是在 JUC、线程池以及框架和中间件的深入理解上还有所欠缺。我们会综合考虑所有候选人的情况,之后会通过邮件或者电话通知你结果。”
问题答案
- Java 中基本数据类型有哪些?
- Java 中的基本数据类型分为四类八种:
- 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节)。
- 布尔类型:boolean(理论上 1 位,但 Java 中没有明确规定)。
- Java 中的基本数据类型分为四类八种:
- 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 Parent {
public void print() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override
public void print() {
System.out.println("Child");
}
}
- String、StringBuilder 和 StringBuffer 有什么区别呢?
- String:是不可变类,一旦创建,其值不能被修改。每次对 String 进行操作(如拼接、替换等),都会创建一个新的 String 对象,这会导致大量的内存开销。例如:
String s = "Hello";
s = s + " World"; // 这里创建了新的 String 对象
- **StringBuilder**:是可变类,线程不安全。它内部使用可变的字符数组来存储字符串,在进行字符串拼接等操作时,不会创建新的对象,而是直接在原数组上进行修改,因此效率较高。例如:
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 直接在原对象上修改
- **StringBuffer**:也是可变类,线程安全。它和 StringBuilder 的功能类似,但它的方法都使用了 synchronized 关键字进行同步,保证了线程安全,但也带来了一定的性能开销。例如:
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World");
- 什么是线程池?为什么要使用线程池?
- 线程池:是一种线程使用模式,它预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完后线程不会销毁,而是返回线程池等待下一个任务。
- 使用线程池的原因:
- 减少线程创建和销毁的开销:线程的创建和销毁需要消耗系统资源,使用线程池可以避免频繁创建和销毁线程,提高系统性能。
- 提高响应速度:任务提交后可以立即从线程池中获取线程执行,无需等待线程创建。
- 控制并发线程数量:可以避免因创建过多线程导致系统资源耗尽,提高系统的稳定性。
- 线程池有哪些重要的参数?
- corePoolSize:核心线程数,线程池会一直保持这些线程存活,即使它们处于空闲状态。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。
- BlockingQueue:阻塞队列,用于存储等待执行的任务。常见的阻塞队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。
- keepAliveTime:线程存活时间,当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务的最长时间,超过这个时间线程会被销毁。
- RejectedExecutionHandler:拒绝策略,当线程池的任务队列已满且线程数量达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(让提交任务的线程执行任务)等。
- 常见的线程池有哪些?
- FixedThreadPool:固定大小的线程池,核心线程数和最大线程数相等,使用 LinkedBlockingQueue 作为任务队列。适用于需要控制并发线程数量的场景。例如:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
- **CachedThreadPool**:可缓存的线程池,核心线程数为 0,最大线程数为 Integer.MAX_VALUE,使用 SynchronousQueue 作为任务队列。当有新任务提交时,如果有空闲线程则使用空闲线程执行,否则创建新线程。适用于执行大量短期异步任务的场景。例如:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- **SingleThreadPool**:单线程的线程池,核心线程数和最大线程数都为 1,使用 LinkedBlockingQueue 作为任务队列。保证所有任务按照顺序依次执行。例如:
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
- **ScheduledThreadPool**:定时任务线程池,可用于执行定时任务或周期性任务。例如:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
- Spring 框架的核心特性有哪些?
- IoC(控制反转):也称为依赖注入(DI),是一种将对象的创建和依赖关系的管理从代码中解耦的设计模式。Spring 通过 IoC 容器来管理对象的生命周期和依赖关系,开发者只需要定义对象和它们之间的依赖关系,Spring 会自动创建和注入对象。例如:
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// 其他方法
}
- **AOP(面向切面编程)**:是一种在不修改原有代码的基础上,对程序的功能进行增强的编程方式。Spring AOP 通过代理模式实现,将一些通用的功能(如日志记录、事务管理等)封装成切面,在程序的特定切入点(如方法调用前后)进行织入。例如:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void beforeMethod() {
System.out.println("Before method execution");
}
}
- Spring Boot 相比 Spring 有什么优势?
- 快速搭建项目:Spring Boot 提供了大量的 Starter 依赖,开发者只需要引入相应的 Starter 依赖,Spring Boot 会自动进行配置,减少了繁琐的配置过程。
- 自动配置:Spring Boot 根据项目中引入的依赖自动进行配置,开发者可以根据需要进行自定义配置,提高了开发效率。
- 内嵌服务器:Spring Boot 内嵌了 Tomcat、Jetty 等服务器,无需手动部署,直接运行主类即可启动项目。
- 生产就绪特性:Spring Boot 提供了一些生产就绪的特性,如健康检查、指标监控等,方便对应用进行管理和监控。
- MyBatis 中 #{} 和 ${} 的区别是什么?
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 进行预编译,能有效防止 SQL 注入。例如:
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是字符串替换,MyBatis 在处理 ${} 时,会将 ${} 直接替换为传入的值,可能会导致 SQL 注入。例如:
<select id="getUserByUsername" parameterType="String" resultType="User">
SELECT * FROM users WHERE username = '${username}'
</select>
因此,在使用 MyBatis 时,尽量使用 #{} 来避免 SQL 注入问题。