《互联网大厂 Java 面试:核心知识、框架与中间件大考验》

38 阅读3分钟

互联网大厂 Java 面试:核心知识、框架与中间件大考验

在一间宽敞明亮的面试室内,气氛略显紧张。严肃的面试官坐在桌前,对面坐着求职者王铁牛。面试正式开始。

第一轮提问 面试官:我们先从 Java 核心知识开始。你能说一下 Java 中基本数据类型有哪些吗? 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那 Java 中多态的实现方式有哪些呢? 王铁牛:多态的实现方式主要有继承和接口。通过子类重写父类的方法,或者实现接口的类实现接口中的方法,就可以实现多态。 面试官:很好。那 String、StringBuilder 和 StringBuffer 有什么区别呢? 王铁牛:String 是不可变的,每次对 String 进行操作都会创建一个新的 String 对象。而 StringBuilder 和 StringBuffer 是可变的,StringBuffer 是线程安全的,StringBuilder 是非线程安全的,在单线程环境下 StringBuilder 的性能更好。 面试官:非常棒,你的基础很扎实。

第二轮提问 面试官:接下来聊聊 JUC 和多线程。什么是线程池?为什么要使用线程池呢? 王铁牛:线程池就是管理一组线程的容器。使用线程池可以减少线程创建和销毁的开销,提高系统的性能,还能更好地控制并发线程的数量,避免资源耗尽。 面试官:不错。那线程池有哪些重要的参数呢? 王铁牛:线程池的重要参数有核心线程数、最大线程数、线程空闲时间、任务队列和拒绝策略。 面试官:那如果任务队列满了,并且线程数达到了最大线程数,这时再提交任务会怎样呢? 王铁牛:呃……这个……好像会根据拒绝策略来处理,具体我有点记不清了。 面试官:这里需要你清晰掌握拒绝策略的几种情况哦。简单来说,常见的拒绝策略有直接抛出异常、丢弃任务、丢弃最老的任务、调用者线程执行任务等。

第三轮提问 面试官:现在我们谈谈一些框架和中间件。Spring 中的 IOC 和 AOP 是什么意思呢? 王铁牛:IOC 是控制反转,把对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象自己创建和管理。AOP 是面向切面编程,它可以在不修改原有代码的基础上,对程序进行增强,比如实现日志记录、事务管理等。 面试官:回答得可以。那 Spring Boot 有什么优点呢? 王铁牛:Spring Boot 可以快速搭建项目,它有自动配置的功能,减少了大量的配置文件,还能很方便地集成各种框架和中间件。 面试官:那 MyBatis 中 #{} 和 {} 的区别是什么呢? **王铁牛**:这个……我只记得它们都可以用来传参,具体区别我不太确定了。 **面试官**:#{} 是预编译处理,它会将参数替换为占位符,能防止 SQL 注入;而 {} 是直接替换,会把参数原样插入到 SQL 语句中,有 SQL 注入的风险。

面试总结 面试官推了推眼镜,说道:“王铁牛,通过这次面试,我能看出你对 Java 的一些基础知识掌握得还可以,像 Java 基本数据类型、多态、线程池的基本概念等都回答得不错,说明你有一定的基础。但在一些知识点的细节和深入理解上还有所欠缺,比如线程池任务队列满和线程数达到最大时的处理细节,MyBatis 中 #{} 和 ${} 的区别等,这些需要你进一步去学习和巩固。另外,对于 Redis、Dubbo、RabbitMq、xxl - job 等内容我们还没深入考察到,说明你在知识的全面性上还有提升空间。回去之后,你可以针对这些薄弱点进行加强学习。我这边会综合考虑你的整体表现,你先回家等通知吧。”

问题答案详解

  1. Java 基本数据类型
    • Java 中有 8 种基本数据类型,可分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(理论上 1 位,但实际实现中通常占 1 字节)。这些基本数据类型是 Java 编程的基础,用于存储不同类型的数据。
  2. 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;
    }
}
// 使用时
Shape shape = new Circle(5);
System.out.println(shape.area());
  1. String、StringBuilder 和 StringBuffer 的区别
    • String:是不可变类,一旦创建,其值不能被修改。每次对 String 进行操作(如拼接、替换等),都会创建一个新的 String 对象,这会导致大量的内存开销,尤其是在频繁操作 String 的场景下。
    • StringBuilder:是可变类,它提供了一系列的方法来修改字符串内容,不会创建新的对象。它是非线程安全的,适合在单线程环境下使用,性能较高。
    • StringBuffer:也是可变类,与 StringBuilder 类似,但它是线程安全的,内部的方法大多使用了 synchronized 关键字进行同步。因此,在多线程环境下使用 StringBuffer 可以保证线程安全,但性能相对较低。
  2. 线程池
    • 定义:线程池是一种线程管理机制,它预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完毕后线程不会销毁,而是返回线程池等待下一个任务。
    • 使用原因
      • 减少线程创建和销毁的开销:线程的创建和销毁需要消耗系统资源,使用线程池可以复用线程,避免了频繁创建和销毁线程带来的性能损耗。
      • 提高系统响应速度:当有任务提交时,线程池中已经有空闲线程可以立即执行任务,无需等待线程的创建。
      • 更好地控制并发线程数量:通过设置线程池的参数,可以控制同时运行的线程数量,避免因线程过多导致系统资源耗尽。
  3. 线程池的重要参数
    • 核心线程数(corePoolSize):线程池中长期保持存活的线程数量。当有任务提交时,线程池会优先使用核心线程来执行任务。
    • 最大线程数(maximumPoolSize):线程池允许创建的最大线程数量。当任务队列已满,且核心线程都在执行任务时,线程池会创建新的线程,直到达到最大线程数。
    • 线程空闲时间(keepAliveTime):当线程池中的线程数量超过核心线程数时,空闲线程在经过 keepAliveTime 时间后会被销毁。
    • 任务队列(workQueue):用于存储等待执行的任务。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
    • 拒绝策略(handler):当任务队列已满,且线程数达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有:
      • AbortPolicy:直接抛出 RejectedExecutionException 异常。
      • DiscardPolicy:直接丢弃新提交的任务。
      • DiscardOldestPolicy:丢弃任务队列中最老的任务,然后尝试提交新任务。
      • CallerRunsPolicy:由调用者线程来执行新提交的任务。
  4. Spring 中的 IOC 和 AOP
    • IOC(控制反转):传统的编程方式中,对象的创建和依赖关系的管理由对象自己负责。而在 Spring 中,IOC 把对象的创建和依赖关系的管理交给 Spring 容器。Spring 容器通过配置文件或注解来创建和管理对象,对象只需要声明自己的依赖,而不需要自己去创建依赖对象。例如:
public class UserService {
    private UserDao userDao;
    // 通过构造函数注入依赖
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
    // 使用 userDao 进行操作
}
- **AOP(面向切面编程)**:AOP 是一种编程范式,它可以在不修改原有代码的基础上,对程序进行增强。AOP 把程序中的一些通用功能(如日志记录、事务管理等)提取出来,形成一个独立的模块,称为切面。在程序运行时,通过代理机制将切面的代码插入到目标方法的前后,实现功能的增强。例如,使用 Spring AOP 实现日志记录:
@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice() {
        System.out.println("Before method execution");
    }
    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice() {
        System.out.println("After method execution");
    }
}
  1. Spring Boot 的优点
    • 快速搭建项目:Spring Boot 提供了一系列的启动器(Starter),通过引入相应的启动器,就可以快速集成各种框架和中间件,减少了繁琐的配置过程。
    • 自动配置:Spring Boot 根据项目中引入的依赖,自动进行配置,大大减少了配置文件的数量。例如,引入 Spring Data JPA 启动器后,Spring Boot 会自动配置数据源和 JPA 相关的配置。
    • 嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 等嵌入式服务器,无需手动部署到外部服务器,直接运行主类即可启动应用。
    • 监控和管理:Spring Boot Actuator 提供了丰富的监控和管理功能,如查看应用的健康状态、内存使用情况、线程信息等。
  2. MyBatis 中 #{} 和 ${} 的区别
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将其替换为占位符(?),然后使用 PreparedStatement 来执行 SQL 语句。这样可以防止 SQL 注入,因为参数会经过预处理,不会直接拼接到 SQL 语句中。例如:
<select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是直接替换,MyBatis 在处理 ${} 时,会将其直接替换为参数的值。这种方式存在 SQL 注入的风险,因为参数是直接拼接到 SQL 语句中的。例如:
<select id="getUserByColumnName" parameterType="String" resultType="User">
    SELECT * FROM users WHERE ${columnName} = 'test'
</select>

因此,在实际开发中,尽量使用 #{} 来传参,避免使用 ${} ,除非在一些特殊情况下,如动态表名、动态列名等。